use std::{
collections::HashMap,
path::Path,
sync::{Arc, Mutex, RwLock},
};
use std::sync::OnceLock;
use cryptoki::error::Error as Pkcs11Error;
use cryptoki::{
context::{CInitializeArgs, CInitializeFlags, Info, Pkcs11},
mechanism::Mechanism,
object::{Attribute, AttributeType, ObjectHandle},
session::{Session, UserType},
slot::{Slot, SlotInfo, TokenInfo},
types::AuthPin,
};
use log::{error, trace};
use crate::commons::crypto::SignerError;
#[derive(Debug, Clone)]
pub(super) struct ThreadSafePkcs11Context(Arc<RwLock<Pkcs11Context>>);
impl std::ops::Deref for ThreadSafePkcs11Context {
type Target = Arc<RwLock<Pkcs11Context>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ThreadSafePkcs11Context {
pub fn new(file_name: &str, ctx: Pkcs11) -> Self {
Self(Arc::new(RwLock::new(Pkcs11Context {
lib_file_name: file_name.to_string(),
ctx: Some(ctx),
initialized: false,
})))
}
}
type Pkcs11ContextsByFileName =
Arc<RwLock<HashMap<String, ThreadSafePkcs11Context>>>;
static CONTEXTS: OnceLock<Pkcs11ContextsByFileName> = OnceLock::new();
#[derive(Debug)]
pub(super) struct Pkcs11Context {
lib_file_name: String,
ctx: Option<Pkcs11>,
initialized: bool,
}
impl Pkcs11Context {
pub fn get_or_load(
lib_path: &Path,
) -> Result<ThreadSafePkcs11Context, SignerError> {
let contexts = CONTEXTS.get_or_init(|| {
Arc::new(RwLock::new(HashMap::new()))
});
let lib_file_name = lib_path.file_name().ok_or_else(|| {
SignerError::Pkcs11Error(format!(
"Failed to load PKCS#11 library '{lib_path:?}': path does not refer to a file"
))
})?;
let lib_file_name = lib_file_name.to_string_lossy().to_string();
let mut locked_contexts = contexts.write().unwrap();
if !locked_contexts.contains_key(&lib_file_name) {
trace!("Loading PKCS#11 library '{lib_path:?}'");
let ctx = Pkcs11::new(lib_path).map_err(|err| {
SignerError::Pkcs11Error(format!(
"Failed to load PKCS#11 library '{lib_path:?}': {err}"
))
})?;
trace!("Loaded PKCS#11 library '{lib_path:?}'");
locked_contexts.insert(
lib_file_name.clone(),
ThreadSafePkcs11Context::new(&lib_file_name, ctx),
);
}
let ctx_ref = locked_contexts.get(&lib_file_name).unwrap();
Ok(ctx_ref.clone())
}
pub fn initialize_if_not_already(&mut self) -> Result<(), SignerError> {
if self.ctx.is_none() {
Err(SignerError::Pkcs11Error(format!(
"Failed to initialize library '{}': Library is not loaded yet",
self.lib_file_name
)))
} else if !self.initialized {
if let Err(err) = self.initialize(
CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK)
) {
error!(
"Failed to initialize PKCS#11 library '{}': {}",
self.lib_file_name, err
);
Err(SignerError::PermanentlyUnusable)
} else {
self.initialized = true;
Ok(())
}
} else {
Ok(())
}
}
pub fn uninitialize_if_not_already(&mut self) {
if self.initialized {
self.initialized = false;
if let Err(err) = self.finalize() {
error!(
"Error uninitializing PKCS#11 library '{}': {}",
self.lib_file_name, err
);
}
CONTEXTS
.get()
.unwrap()
.write()
.unwrap()
.remove(&self.lib_file_name);
}
}
}
impl Pkcs11Context {
fn logged_cryptoki_call<F, T>(
&self,
cryptoki_call_name: &'static str,
call: F,
) -> Result<T, Pkcs11Error>
where
F: FnOnce(&Pkcs11) -> Result<T, Pkcs11Error>,
{
trace!("{}::{}()", self.lib_file_name, cryptoki_call_name);
let res = (call)(self.ctx.as_ref().unwrap());
if let Err(err) = &res {
error!(
"{}::{}() failed: {}",
self.lib_file_name, cryptoki_call_name, err
);
}
res
}
fn logged_cryptoki_call_with_take<F, T>(
&mut self,
cryptoki_call_name: &'static str,
call: F,
) -> Result<T, Pkcs11Error>
where
F: FnOnce(Pkcs11) -> Result<T, Pkcs11Error>,
{
trace!("{}::{}()", self.lib_file_name, cryptoki_call_name);
let ctx = self.ctx.take().unwrap(); (call)(ctx)
}
}
impl Pkcs11Context {
fn initialize(
&self,
init_args: CInitializeArgs,
) -> Result<(), Pkcs11Error> {
self.logged_cryptoki_call("Initialize", |cryptoki| {
cryptoki.initialize(init_args)
})
}
fn finalize(&mut self) -> Result<(), Pkcs11Error> {
self.logged_cryptoki_call_with_take("Finalize", |cryptoki| {
cryptoki.finalize()
})
}
pub fn get_info(&self) -> Result<Info, Pkcs11Error> {
self.logged_cryptoki_call("GetLibraryInfo", |cryptoki| {
cryptoki.get_library_info()
})
}
pub fn get_slot_list(
&self,
token_present: bool,
) -> Result<Vec<Slot>, Pkcs11Error> {
self.logged_cryptoki_call("GetSlotList", |cryptoki| {
if token_present {
cryptoki.get_slots_with_initialized_token()
} else {
cryptoki.get_all_slots()
}
})
}
pub fn get_slot_info(&self, slot: Slot) -> Result<SlotInfo, Pkcs11Error> {
self.logged_cryptoki_call("GetSlotInfo", |cryptoki| {
cryptoki.get_slot_info(slot)
})
}
pub fn get_token_info(
&self,
slot: Slot,
) -> Result<TokenInfo, Pkcs11Error> {
self.logged_cryptoki_call("GetTokenInfo", |cryptoki| {
cryptoki.get_token_info(slot)
})
}
pub fn open_rw_session(
&self,
slot: Slot,
) -> Result<Session, Pkcs11Error> {
self.logged_cryptoki_call("OpenSession", |cryptoki| {
cryptoki.open_rw_session(slot)
})
}
pub fn generate_key_pair(
&self,
session: Arc<Mutex<Session>>,
mechanism: &Mechanism,
public_key_template: &[Attribute],
private_key_template: &[Attribute],
) -> Result<(ObjectHandle, ObjectHandle), Pkcs11Error> {
self.logged_cryptoki_call("GenerateKeyPair", |_| {
session.lock().unwrap().generate_key_pair(
mechanism,
public_key_template,
private_key_template,
)
})
}
pub fn get_attributes(
&self,
session: Arc<Mutex<Session>>,
object: ObjectHandle,
template: &[AttributeType],
) -> Result<Vec<Attribute>, Pkcs11Error> {
self.logged_cryptoki_call("GetAttributes", move |_| {
session.lock().unwrap().get_attributes(object, template)
})
}
pub fn login(
&self,
session: Arc<Mutex<Session>>,
user_type: UserType,
pin: Option<&AuthPin>,
) -> Result<(), Pkcs11Error> {
self.logged_cryptoki_call("Login", |_| {
session.lock().unwrap().login(user_type, pin)
})
}
pub fn sign(
&self,
session: Arc<Mutex<Session>>,
mechanism: &Mechanism,
key: ObjectHandle,
data: &[u8],
) -> Result<Vec<u8>, Pkcs11Error> {
self.logged_cryptoki_call("Sign", |_| {
session.lock().unwrap().sign(mechanism, key, data)
})
}
pub fn find_objects(
&self,
session: Arc<Mutex<Session>>,
template: &[Attribute],
) -> Result<Vec<ObjectHandle>, Pkcs11Error> {
self.logged_cryptoki_call("FindObjects", |_| {
session.lock().unwrap().find_objects(template)
})
}
pub fn destroy_object(
&self,
session: Arc<Mutex<Session>>,
object_handle: ObjectHandle,
) -> Result<(), Pkcs11Error> {
self.logged_cryptoki_call("DestroyObject", |_| {
session.lock().unwrap().destroy_object(object_handle)
})
}
}