diff --git a/pkcs11/src/api/mod.rs b/pkcs11/src/api/mod.rs index 4af5f7a2..4e2cfe2c 100644 --- a/pkcs11/src/api/mod.rs +++ b/pkcs11/src/api/mod.rs @@ -14,7 +14,8 @@ pub mod token; pub mod verify; use crate::{ - data::{self, DEVICE}, + backend::events::{fetch_slots_state, EventsManager}, + data::{self, DEVICE, EVENTS_MANAGER, TOKENS_STATE}, defs, padded_str, }; use cryptoki_sys::{CK_INFO, CK_INFO_PTR, CK_RV, CK_VOID_PTR}; @@ -72,6 +73,12 @@ pub extern "C" fn C_Initialize(pInitArgs: CK_VOID_PTR) -> CK_RV { } } + // Initialize the events manager + *EVENTS_MANAGER.write().unwrap() = EventsManager::new(); + *TOKENS_STATE.lock().unwrap() = std::collections::HashMap::new(); + + fetch_slots_state(); + cryptoki_sys::CKR_OK } @@ -80,6 +87,8 @@ pub extern "C" fn C_Finalize(pReserved: CK_VOID_PTR) -> CK_RV { if !pReserved.is_null() { return cryptoki_sys::CKR_ARGUMENTS_BAD; } + EVENTS_MANAGER.write().unwrap().finalized = true; + cryptoki_sys::CKR_OK } diff --git a/pkcs11/src/api/token.rs b/pkcs11/src/api/token.rs index 53e823f3..d26752ef 100644 --- a/pkcs11/src/api/token.rs +++ b/pkcs11/src/api/token.rs @@ -3,14 +3,18 @@ use cryptoki_sys::{ CK_TOKEN_INFO, CK_ULONG, }; use log::{debug, error, trace, warn}; -use nethsm_sdk_rs::{apis::default_api, models::SystemState}; +use nethsm_sdk_rs::{ + apis::default_api, + models::{HealthStateData, InfoData, SystemState}, +}; use crate::{ backend::{ + events::fetch_slots_state, login::{LoginCtx, UserMode}, slot::get_slot, }, - data::DEVICE, + data::{DEVICE, EVENTS_MANAGER}, defs::{DEFAULT_FIRMWARE_VERSION, DEFAULT_HARDWARE_VERSION, MECHANISM_LIST}, lock_mutex, lock_session, padded_str, version_struct_from_str, }; @@ -89,13 +93,16 @@ pub extern "C" fn C_GetSlotInfo( crate::backend::login::UserMode::Guest, ); - // // fetch info from the device + // fetch info from the device let info = match result { - Ok(info) => info, + Ok(info) => info.entity, Err(e) => { - error!("Error getting info: {:?}", e); - return cryptoki_sys::CKR_FUNCTION_FAILED; + trace!("Error getting info: {:?}", e); + InfoData { + product: "unknown".to_string(), + vendor: "unknown".to_string(), + } } }; @@ -107,20 +114,22 @@ pub extern "C" fn C_GetSlotInfo( // fetch the sysem state let system_state = match result { - Ok(info) => info, + Ok(info) => info.entity, Err(e) => { - error!("Error getting system state: {:?}", e); - return cryptoki_sys::CKR_FUNCTION_FAILED; + trace!("Error getting system state: {:?}", e); + HealthStateData { + state: SystemState::Unprovisioned, + } } }; - if system_state.entity.state == SystemState::Operational { + if system_state.state == SystemState::Operational { flags |= cryptoki_sys::CKF_TOKEN_PRESENT; } let info: CK_SLOT_INFO = CK_SLOT_INFO { - slotDescription: padded_str!("info.entity.product", 64), - manufacturerID: padded_str!("info.entity.vendor", 32), + slotDescription: padded_str!(info.product, 64), + manufacturerID: padded_str!(info.vendor, 32), flags, hardwareVersion: DEFAULT_HARDWARE_VERSION, firmwareVersion: DEFAULT_FIRMWARE_VERSION, @@ -351,17 +360,141 @@ pub extern "C" fn C_WaitForSlotEvent( pReserved: cryptoki_sys::CK_VOID_PTR, ) -> cryptoki_sys::CK_RV { trace!("C_WaitForSlotEvent() called"); - cryptoki_sys::CKR_FUNCTION_NOT_SUPPORTED + + if pSlot.is_null() { + return cryptoki_sys::CKR_ARGUMENTS_BAD; + } + + fetch_slots_state(); + + loop { + // check if there is an event in the queue + + let slot = EVENTS_MANAGER.write().unwrap().events.pop(); + if let Some(slot) = slot { + unsafe { + std::ptr::write(pSlot, slot); + } + return cryptoki_sys::CKR_OK; + } + + // if the dont block flag is set, return no event + if flags & cryptoki_sys::CKF_DONT_BLOCK == 1 { + return cryptoki_sys::CKR_NO_EVENT; + } else { + // Otherwise, wait for an event + + // If C_Finalize() has been called, return an error + if EVENTS_MANAGER.read().unwrap().finalized { + return cryptoki_sys::CKR_CRYPTOKI_NOT_INITIALIZED; + } + + // sleep for 1 second + std::thread::sleep(std::time::Duration::from_secs(1)); + + // fetch the slots state so we get the latest events in the next iteration + fetch_slots_state(); + } + } } #[cfg(test)] mod tests { - use cryptoki_sys::{CKU_USER, CK_MECHANISM_INFO}; - - use crate::{backend::slot::set_test_config_env, data::SESSION_MANAGER}; + use cryptoki_sys::{CKF_DONT_BLOCK, CKU_USER, CK_MECHANISM_INFO}; + + use crate::{ + api::C_Finalize, + backend::{ + events::{update_slot_state, EventsManager}, + slot::set_test_config_env, + }, + data::{SESSION_MANAGER, TOKENS_STATE}, + }; use super::*; + #[test] + fn test_wait_for_slot_event_no_event() { + set_test_config_env(); + *EVENTS_MANAGER.write().unwrap() = EventsManager::new(); + *TOKENS_STATE.lock().unwrap() = std::collections::HashMap::new(); + + let mut slot = 0; + let result = C_WaitForSlotEvent(CKF_DONT_BLOCK, &mut slot, std::ptr::null_mut()); + assert_eq!(result, cryptoki_sys::CKR_NO_EVENT); + } + + #[test] + fn test_wait_for_slot_event_one_event() { + set_test_config_env(); + *EVENTS_MANAGER.write().unwrap() = EventsManager::new(); + *TOKENS_STATE.lock().unwrap() = std::collections::HashMap::new(); + + update_slot_state(0, false); + update_slot_state(0, true); + + println!("Events: {:?}", EVENTS_MANAGER.read().unwrap().events); + + let mut slot = 15; + let result = C_WaitForSlotEvent(CKF_DONT_BLOCK, &mut slot, std::ptr::null_mut()); + assert_eq!(result, cryptoki_sys::CKR_OK); + assert_eq!(slot, 0); + } + + // we ignore this test because it requires cargo test -- --test-threads=1 + #[test] + #[ignore] + fn test_wait_for_slot_event_blocking_one_event() { + set_test_config_env(); + *EVENTS_MANAGER.write().unwrap() = EventsManager::new(); + *TOKENS_STATE.lock().unwrap() = std::collections::HashMap::new(); + + // update the slot state in a separate thread + + let handle = std::thread::spawn(|| { + std::thread::sleep(std::time::Duration::from_millis(100)); + update_slot_state(0, false); + update_slot_state(0, true); + }); + + let mut slot = 15; + let result = C_WaitForSlotEvent(0, &mut slot, std::ptr::null_mut()); + handle.join().unwrap(); + assert_eq!(result, cryptoki_sys::CKR_OK); + assert_eq!(slot, 0); + } + + // we ignore this test because it requires cargo test -- --test-threads=1 + #[test] + #[ignore] + fn test_wait_for_slot_event_blocking_finalize() { + set_test_config_env(); + *EVENTS_MANAGER.write().unwrap() = EventsManager::new(); + *TOKENS_STATE.lock().unwrap() = std::collections::HashMap::new(); + + // update the slot state in a separate thread + + let handle = std::thread::spawn(|| { + std::thread::sleep(std::time::Duration::from_millis(100)); + + C_Finalize(std::ptr::null_mut()); + }); + + let mut slot = 15; + let result = C_WaitForSlotEvent(0, &mut slot, std::ptr::null_mut()); + handle.join().unwrap(); + println!("slot: {}", slot); + assert_eq!(result, cryptoki_sys::CKR_CRYPTOKI_NOT_INITIALIZED); + } + + #[test] + fn test_wait_for_slot_event_null_slot_ptr() { + set_test_config_env(); + + let result = C_WaitForSlotEvent(CKF_DONT_BLOCK, std::ptr::null_mut(), std::ptr::null_mut()); + assert_eq!(result, cryptoki_sys::CKR_ARGUMENTS_BAD); + } + #[test] fn test_get_slot_list_null_count() { let result = C_GetSlotList(0, std::ptr::null_mut(), std::ptr::null_mut()); @@ -537,10 +670,4 @@ mod tests { let result = C_InitToken(0, std::ptr::null_mut(), 0, std::ptr::null_mut()); assert_eq!(result, cryptoki_sys::CKR_FUNCTION_NOT_SUPPORTED); } - - #[test] - fn test_wait_for_slot_event() { - let result = C_WaitForSlotEvent(0, std::ptr::null_mut(), std::ptr::null_mut()); - assert_eq!(result, cryptoki_sys::CKR_FUNCTION_NOT_SUPPORTED); - } } diff --git a/pkcs11/src/backend/encrypt.rs b/pkcs11/src/backend/encrypt.rs index 0661fa07..fb37b78e 100644 --- a/pkcs11/src/backend/encrypt.rs +++ b/pkcs11/src/backend/encrypt.rs @@ -134,7 +134,7 @@ fn encrypt_data( login::UserMode::Operator, ) .map_err(|err| { - if let Error::Api(ApiError::ResponseError(ref resp)) = err { + if let ApiError::ResponseError(ref resp) = err { if resp.status == 400 { if resp.content.contains("argument length") { return Error::InvalidDataLength; @@ -142,7 +142,7 @@ fn encrypt_data( return Error::InvalidData; } } - err + err.into() })?; Ok(Base64::decode_vec(&output.entity.encrypted)?) diff --git a/pkcs11/src/backend/events.rs b/pkcs11/src/backend/events.rs new file mode 100644 index 00000000..2b373224 --- /dev/null +++ b/pkcs11/src/backend/events.rs @@ -0,0 +1,50 @@ +use cryptoki_sys::CK_SLOT_ID; +use nethsm_sdk_rs::{apis::default_api, models::SystemState}; + +use crate::data::{DEVICE, EVENTS_MANAGER, TOKENS_STATE}; + +use super::login::LoginCtx; + +pub struct EventsManager { + pub events: Vec, // list of slots that changed + + // Used when CKF_DONT_BLOCK is clear and C_Finalize is called, then every blocking call to C_WaitForSlotEvent should return CKR_CRYPTOKI_NOT_INITIALIZED + pub finalized: bool, +} + +impl EventsManager { + pub fn new() -> Self { + EventsManager { + events: Vec::new(), + finalized: false, + } + } +} + +pub fn update_slot_state(slot_id: CK_SLOT_ID, present: bool) { + let mut tokens_state = TOKENS_STATE.lock().unwrap(); + if let Some(prev) = tokens_state.get(&slot_id) { + if *prev == present { + return; + } else { + // new event + EVENTS_MANAGER.write().unwrap().events.push(slot_id); + } + } + tokens_state.insert(slot_id, present); +} + +pub fn fetch_slots_state() { + for (index, slot) in DEVICE.slots.iter().enumerate() { + let mut login_ctx = LoginCtx::new(None, None, slot.instances.clone()); + let status = login_ctx + .try_( + |conf| default_api::health_state_get(&conf), + super::login::UserMode::Guest, + ) + .map(|state| state.entity.state == SystemState::Operational) + .unwrap_or(false); + + update_slot_state(index as CK_SLOT_ID, status); + } +} diff --git a/pkcs11/src/backend/key.rs b/pkcs11/src/backend/key.rs index 48a94416..7b0f5310 100644 --- a/pkcs11/src/backend/key.rs +++ b/pkcs11/src/backend/key.rs @@ -489,14 +489,11 @@ pub fn fetch_key( debug!("Failed to fetch key {}: {:?}", key_id, err); if matches!( err, - Error::Api(ApiError::ResponseError(backend::ResponseContent { - status: 404, - .. - })) + ApiError::ResponseError(backend::ResponseContent { status: 404, .. }) ) { return Ok(vec![]); } - return Err(err); + return Err(err.into()); } }; diff --git a/pkcs11/src/backend/login.rs b/pkcs11/src/backend/login.rs index 78999629..386fb5ea 100644 --- a/pkcs11/src/backend/login.rs +++ b/pkcs11/src/backend/login.rs @@ -12,7 +12,7 @@ use std::fmt::Debug; use crate::config::config_file::UserConfig; -use super::Error; +use super::{ApiError, Error}; #[derive(Debug, Clone)] pub struct LoginCtx { @@ -65,11 +65,6 @@ impl LoginCtx { administrator: Option, instances: Vec, ) -> Self { - trace!( - "Creating login context with administrator: {:?}", - administrator - ); - let mut ck_state = CKS_RO_PUBLIC_SESSION; let firt_instance = instances.first(); @@ -193,7 +188,7 @@ impl LoginCtx { } // Try to run the api call on each instance until one succeeds - pub fn try_(&mut self, api_call: F, user_mode: UserMode) -> Result + pub fn try_(&mut self, api_call: F, user_mode: UserMode) -> Result where F: FnOnce(Configuration) -> Result> + Clone, { @@ -219,7 +214,7 @@ impl LoginCtx { Err(err) => return Err(err.into()), } } - Err(Error::NoInstance) + Err(ApiError::NoInstance) } pub fn ck_state(&self) -> CK_STATE { diff --git a/pkcs11/src/backend/mod.rs b/pkcs11/src/backend/mod.rs index e4baa719..85f45c42 100644 --- a/pkcs11/src/backend/mod.rs +++ b/pkcs11/src/backend/mod.rs @@ -17,6 +17,7 @@ use nethsm_sdk_rs::apis; pub mod db; pub mod decrypt; pub mod encrypt; +pub mod events; pub mod key; pub mod login; pub mod mechanism; @@ -37,6 +38,7 @@ pub enum ApiError { Serde(serde_json::Error), Io(std::io::Error), ResponseError(ResponseContent), + NoInstance, } impl From> for ApiError { @@ -65,7 +67,6 @@ pub enum Error { ObjectClassNotSupported, InvalidMechanismMode(MechMode, Mechanism), Api(ApiError), - NoInstance, Base64Error(base64ct::Error), StringParse(std::string::FromUtf8Error), Login(LoginError), @@ -79,6 +80,12 @@ pub enum Error { InvalidEncryptedDataLength, } +impl From for Error { + fn from(err: ApiError) -> Self { + Error::Api(err) + } +} + impl From> for Error { fn from(_: PoisonError) -> Self { Error::DbLock @@ -125,9 +132,9 @@ impl From for CK_RV { Error::NotLoggedIn(_) => CKR_USER_NOT_LOGGED_IN, Error::InvalidMechanism(_, _) => CKR_MECHANISM_INVALID, Error::InvalidMechanismMode(_, _) => CKR_MECHANISM_INVALID, - Error::NoInstance => CKR_DEVICE_ERROR, Error::Base64Error(_) | Error::StringParse(_) => CKR_DEVICE_ERROR, Error::Api(err) => match err { + ApiError::NoInstance => CKR_TOKEN_NOT_PRESENT, ApiError::Ureq(_) => CKR_DEVICE_ERROR, ApiError::Io(_) => CKR_DEVICE_ERROR, ApiError::Serde(_) => CKR_DEVICE_ERROR, @@ -178,6 +185,7 @@ impl std::fmt::Display for Error { format!("Unable to use mechanim {:?} for {:?}", mechanism, mode) } Error::Api(err) => match err { + ApiError::NoInstance => "No valid instance in the slot".to_string(), ApiError::Ureq(err) => format!("Request error : {}", err), ApiError::Serde(err) => format!("Serde error: {:?}", err), ApiError::Io(err) => format!("IO error: {:?}", err), @@ -188,7 +196,6 @@ impl std::fmt::Display for Error { _ => format!("Api error: {:?}", resp), }, }, - Error::NoInstance => "No instance".to_string(), Error::Base64Error(err) => format!("Base64 Decode error: {:?}", err), Error::StringParse(err) => format!("String parse error: {:?}", err), }; diff --git a/pkcs11/src/config/initialization.rs b/pkcs11/src/config/initialization.rs index 42d0fe80..78259dff 100644 --- a/pkcs11/src/config/initialization.rs +++ b/pkcs11/src/config/initialization.rs @@ -26,7 +26,6 @@ pub fn initialize_configuration() -> Result { for slot in config.slots.iter() { slots.push(Arc::new(slot_from_config(slot)?)); } - Ok(Device { slots, log_file: config.log_file, diff --git a/pkcs11/src/data.rs b/pkcs11/src/data.rs index 65e21922..40228eb1 100644 --- a/pkcs11/src/data.rs +++ b/pkcs11/src/data.rs @@ -1,11 +1,13 @@ -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; + +use crate::backend::events::EventsManager; use crate::{ api, backend::session::SessionManager, config::{self, device::Device}, }; -use cryptoki_sys::{CK_FUNCTION_LIST, CK_VERSION}; +use cryptoki_sys::{CK_FUNCTION_LIST, CK_SLOT_ID, CK_VERSION}; use lazy_static::lazy_static; pub const DEVICE_VERSION: CK_VERSION = CK_VERSION { major: 2, @@ -26,6 +28,10 @@ lazy_static! { // As we are using lazy_static, this field will be initialized the first time it's used. // The key of the map is the name the application tries to use, the value is the name given by the NetHSM. pub static ref KEY_ALIASES : Arc>> = Arc::new(Mutex::new(std::collections::HashMap::new())); + // Storage of events + pub static ref EVENTS_MANAGER : Arc> = Arc::new(RwLock::new(EventsManager::new())); + // Token present or not (true = present) + pub static ref TOKENS_STATE : Arc>> = Arc::new(Mutex::new(std::collections::HashMap::new())); } pub static mut FN_LIST: CK_FUNCTION_LIST = CK_FUNCTION_LIST { version: DEVICE_VERSION, diff --git a/tools/test_profiling.sh b/tools/test_profiling.sh index 4b7e3b08..e1e0c857 100755 --- a/tools/test_profiling.sh +++ b/tools/test_profiling.sh @@ -6,7 +6,7 @@ export LLVM_PROFILE_FILE="${PWD}/profile/%p-%m.profraw" rm -rf _test_objects -RUSTFLAGS="-C instrument-coverage" cargo test --all-features --all-targets +RUSTFLAGS="-C instrument-coverage" cargo test --all-features --all-targets -- --test-threads=1 files=$(RUSTFLAGS="-C instrument-coverage" cargo test --tests --no-run --message-format=json | jq -r "select(.profile.test == true) | .filenames[]" | grep -v dSYM - )