Skip to main content

kipuka_hsm/
pkcs11.rs

1//! PKCS#11 library context management.
2
3use crate::error::{HsmError, HsmResult};
4use cryptoki::context::{CInitializeArgs, Pkcs11};
5use std::path::Path;
6use std::sync::{Arc, Mutex};
7
8/// PKCS#11 library context.
9///
10/// Wraps the Cryptoki `Pkcs11` handle with thread-safe reference counting
11/// and automatic initialization/finalization.
12#[derive(Clone)]
13pub struct Pkcs11Context {
14    inner: Arc<Mutex<Option<Pkcs11>>>,
15}
16
17impl Pkcs11Context {
18    /// Initialize a new PKCS#11 context from a library path.
19    ///
20    /// # Arguments
21    ///
22    /// * `library_path` - Path to the PKCS#11 library (.so, .dylib, .dll)
23    ///
24    /// # Errors
25    ///
26    /// Returns `HsmError::LibraryLoad` if the library cannot be loaded or initialized.
27    pub fn new(library_path: impl AsRef<Path>) -> HsmResult<Self> {
28        let path = library_path.as_ref();
29
30        tracing::debug!("Loading PKCS#11 library from {}", path.display());
31
32        let pkcs11 = Pkcs11::new(path).map_err(|e| {
33            HsmError::LibraryLoad(format!("Failed to load {}: {}", path.display(), e))
34        })?;
35
36        // Initialize the library with OS locking
37        pkcs11
38            .initialize(CInitializeArgs::OsThreads)
39            .map_err(|e| HsmError::LibraryLoad(format!("C_Initialize failed: {e}")))?;
40
41        tracing::info!("PKCS#11 library initialized: {}", path.display());
42
43        Ok(Self {
44            inner: Arc::new(Mutex::new(Some(pkcs11))),
45        })
46    }
47
48    /// Get the underlying Pkcs11 handle.
49    ///
50    /// # Panics
51    ///
52    /// Panics if the mutex is poisoned (should never happen in normal operation).
53    /// Execute a closure with the PKCS#11 handle.
54    ///
55    /// Returns `HsmError::LibraryLoad` if this is a placeholder context.
56    pub fn with_pkcs11<F, R>(&self, f: F) -> HsmResult<R>
57    where
58        F: FnOnce(&Pkcs11) -> HsmResult<R>,
59    {
60        let guard = self.inner.lock().expect("Pkcs11Context mutex poisoned");
61        let pkcs11 = guard.as_ref().ok_or_else(|| {
62            HsmError::LibraryLoad("HSM not initialized (placeholder context)".into())
63        })?;
64        f(pkcs11)
65    }
66
67    /// Get library information.
68    pub fn library_info(&self) -> HsmResult<String> {
69        self.with_pkcs11(|pkcs11| {
70            let info = pkcs11
71                .get_library_info()
72                .map_err(|e| HsmError::LibraryLoad(format!("Failed to get library info: {e}")))?;
73            Ok(format!(
74                "Library: {} v{}.{}",
75                info.library_description(),
76                info.cryptoki_version().major(),
77                info.cryptoki_version().minor()
78            ))
79        })
80    }
81
82    /// Create a placeholder context for use when HSM is not configured.
83    ///
84    /// Any PKCS#11 operation on a placeholder will return `HsmError::LibraryLoad`.
85    pub fn placeholder() -> Self {
86        Self {
87            inner: Arc::new(Mutex::new(None)),
88        }
89    }
90}
91
92impl Drop for Pkcs11Context {
93    fn drop(&mut self) {
94        // Finalization is handled automatically by cryptoki 0.7
95        // The library calls C_Finalize in its own Drop implementation
96        tracing::debug!("PKCS#11 context dropped");
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    #[ignore = "requires PKCS#11 library"]
106    fn test_context_creation() {
107        // This would require a real PKCS#11 library path
108        let result = Pkcs11Context::new("/usr/local/lib/softhsm/libsofthsm2.so");
109        assert!(result.is_ok() || matches!(result, Err(HsmError::LibraryLoad(_))));
110    }
111}