kipuka_hsm/lib.rs
1//! PKCS#11 HSM abstraction with ML-DSA and ML-KEM support.
2//!
3//! This crate provides a high-level interface to PKCS#11 Hardware Security Modules (HSMs)
4//! with full support for:
5//!
6//! - **Classical algorithms**: RSA (2048/3072/4096), ECDSA (P-256/P-384/P-521)
7//! - **Post-quantum algorithms**: ML-DSA (FIPS 204), ML-KEM (FIPS 203)
8//! - **Key wrapping**: AES Key Wrap (RFC 3394), RSAES-OAEP
9//! - **NIAP CA PP compliance**: FCS_CKM.1 key generation requirements
10//!
11//! # Supported Providers
12//!
13//! - Entrust nShield
14//! - Utimaco CryptoServer
15//! - Kryoptic (software token)
16//! - Thales Luna Cloud HSM (CSP)
17//! - Thales Luna Tactical (TCT)
18//!
19//! # Post-Quantum Cryptography
20//!
21//! PQC mechanisms (ML-DSA, ML-KEM) are vendor-specific until PKCS#11 v3.2 standardization.
22//! Each provider configuration includes vendor-specific mechanism IDs via `PqcMechanismIds`.
23//!
24//! When HSM does not support PQC, the library can fall back to software implementations
25//! using `synta-certificate`.
26//!
27//! # Architecture
28//!
29//! ```text
30//! ┌─────────────────────────────────────────────────────────┐
31//! │ Application Layer │
32//! │ (kipuka EST server) │
33//! └─────────────────────────────────────────────────────────┘
34//! │
35//! ▼
36//! ┌─────────────────────────────────────────────────────────┐
37//! │ kipuka-hsm crate │
38//! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
39//! │ │ HsmKeyPair │ │ HsmSigner │ │ HsmSlot │ │
40//! │ └──────────────┘ └──────────────┘ └──────────────┘ │
41//! │ │ │ │ │
42//! │ └──────────────┴──────────────────┘ │
43//! │ │ │
44//! │ ┌────────▼────────┐ │
45//! │ │ Pkcs11Context │ │
46//! │ └────────┬────────┘ │
47//! └───────────────────────────┼──────────────────────────────┘
48//! │
49//! ▼
50//! ┌─────────────────────────────────────────────────────────┐
51//! │ cryptoki crate │
52//! │ (Rust PKCS#11 bindings) │
53//! └─────────────────────────────────────────────────────────┘
54//! │
55//! ▼
56//! ┌─────────────────────────────────────────────────────────┐
57//! │ Vendor PKCS#11 Library (.so/.dll) │
58//! │ (Entrust, Utimaco, Kryoptic, Thales CSP/TCT) │
59//! └─────────────────────────────────────────────────────────┘
60//! │
61//! ▼
62//! ┌─────────────────────────────────────────────────────────┐
63//! │ Hardware Security Module │
64//! │ (Physical HSM or software token) │
65//! └─────────────────────────────────────────────────────────┘
66//! ```
67//!
68//! # Example Usage
69//!
70//! ```rust,no_run
71//! use kipuka_hsm::{
72//! HsmSlot, HsmKeyPair, KeyAlgorithm, EcdsaCurve,
73//! Pkcs11Context, sign_ecdsa,
74//! providers::HsmProvider,
75//! key::PqcMechanismIds,
76//! };
77//!
78//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
79//! // Initialize PKCS#11 library
80//! let provider = HsmProvider::Kryoptic;
81//! let config = provider.config();
82//! let context = Pkcs11Context::new(&config.library_path)?;
83//!
84//! // Find HSM slot
85//! let slot = HsmSlot::find_first_slot(&context)?;
86//!
87//! // Generate ECDSA P-256 key pair
88//! let pqc_mechanisms = PqcMechanismIds::default();
89//! let key = HsmKeyPair::generate(
90//! &slot,
91//! KeyAlgorithm::Ecdsa(EcdsaCurve::P256),
92//! "my-signing-key",
93//! &[0x01, 0x02, 0x03], // CKA_ID
94//! &config,
95//! &pqc_mechanisms,
96//! )?;
97//!
98//! // Sign a message digest
99//! let digest = [0u8; 32]; // SHA-256 digest
100//! let signature = sign_ecdsa(&key, &digest)?;
101//! # Ok(())
102//! # }
103//! ```
104//!
105//! # ML-DSA Example
106//!
107//! ```rust,no_run
108//! use kipuka_hsm::{
109//! HsmSlot, HsmKeyPair, KeyAlgorithm, MlDsaLevel,
110//! Pkcs11Context, sign_ml_dsa,
111//! providers::HsmProvider,
112//! key::PqcMechanismIds,
113//! };
114//!
115//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
116//! let provider = HsmProvider::Kryoptic;
117//! let config = provider.config();
118//! let context = Pkcs11Context::new(&config.library_path)?;
119//! let slot = HsmSlot::find_first_slot(&context)?;
120//!
121//! // Configure vendor-specific PQC mechanism IDs
122//! let pqc_mechanisms = PqcMechanismIds {
123//! ml_dsa_keygen: Some(0x8000_0001),
124//! ml_dsa_65: Some(0x8000_0003),
125//! ..Default::default()
126//! };
127//!
128//! // Generate ML-DSA-65 key pair
129//! let key = HsmKeyPair::generate(
130//! &slot,
131//! KeyAlgorithm::MlDsa(MlDsaLevel::L3),
132//! "ml-dsa-signing-key",
133//! &[0x04, 0x05, 0x06],
134//! &config,
135//! &pqc_mechanisms,
136//! )?;
137//!
138//! // Sign a message (ML-DSA hashes internally)
139//! let message = b"Hello, post-quantum world!";
140//! let signature = sign_ml_dsa(&key, message, MlDsaLevel::L3, &pqc_mechanisms)?;
141//! # Ok(())
142//! # }
143//! ```
144
145// Core modules
146pub mod error;
147pub mod key;
148pub mod pkcs11;
149pub mod sign;
150pub mod slot;
151
152// Provider registry
153pub mod providers;
154
155// Re-exports for convenience
156pub use error::{HsmError, HsmResult};
157pub use key::{EcdsaCurve, HsmKeyPair, KeyAlgorithm, MlDsaLevel, MlKemLevel, PqcMechanismIds};
158pub use pkcs11::Pkcs11Context;
159pub use providers::HsmProvider;
160pub use sign::{
161 DefaultHsmSigner, HsmSigner, RsaHashAlgorithm, SoftwarePqcFallback, sign_ecdsa, sign_ml_dsa,
162 sign_rsa_pkcs1, sign_rsa_pss,
163};
164pub use slot::HsmSlot;
165
166/// High-level HSM context wrapping PKCS#11 initialization and provider config.
167///
168/// Used by `AppState` to hold the HSM connection for the server lifetime.
169/// When fully initialized, holds a logged-in PKCS#11 session for signing.
170pub struct HsmContext {
171 pub context: Pkcs11Context,
172 pub provider: HsmProvider,
173 /// Active logged-in session for signing operations.
174 ///
175 /// Wrapped in a `Mutex` because `Session` is not `Send`+`Sync` and
176 /// signing requires `&Session` (which takes a lock internally).
177 session: std::sync::Mutex<Option<cryptoki::session::Session>>,
178 /// The slot used for this context (needed for opening new sessions).
179 #[allow(dead_code)]
180 slot: Option<HsmSlot>,
181}
182
183// Safety: The `Session` inside `Mutex` is only accessed while locked.
184// The cryptoki `Session` is `!Send` but we only use it from within a
185// synchronous `Mutex::lock()` guard, which is safe for `Send`+`Sync`.
186unsafe impl Send for HsmContext {}
187unsafe impl Sync for HsmContext {}
188
189impl HsmContext {
190 /// Create a new HSM context with a logged-in session ready for signing.
191 pub fn new(
192 context: Pkcs11Context,
193 provider: HsmProvider,
194 slot: HsmSlot,
195 session: cryptoki::session::Session,
196 ) -> Self {
197 Self {
198 context,
199 provider,
200 session: std::sync::Mutex::new(Some(session)),
201 slot: Some(slot),
202 }
203 }
204
205 pub fn placeholder() -> Self {
206 Self {
207 context: Pkcs11Context::placeholder(),
208 provider: HsmProvider::Kryoptic,
209 session: std::sync::Mutex::new(None),
210 slot: None,
211 }
212 }
213
214 /// Sign data using the HSM key identified by label.
215 ///
216 /// Uses `CKM_SHA256_RSA_PKCS` for RSA keys (the mechanism hashes
217 /// and signs in one operation, so `data` is the raw TBS bytes).
218 ///
219 /// # Arguments
220 ///
221 /// * `key_label` - CKA_LABEL of the private key in the token
222 /// * `data` - data to sign (raw TBS certificate bytes)
223 /// * `hash_algorithm` - hash algorithm name ("sha256", "sha384", "sha512")
224 pub fn sign_data(
225 &self,
226 key_label: &str,
227 data: &[u8],
228 hash_algorithm: &str,
229 ) -> HsmResult<Vec<u8>> {
230 use cryptoki::mechanism::Mechanism;
231 use cryptoki::object::{Attribute, ObjectClass};
232
233 let guard = self
234 .session
235 .lock()
236 .expect("HsmContext session mutex poisoned");
237 let session = guard.as_ref().ok_or_else(|| {
238 HsmError::LibraryLoad("HSM session not initialized (placeholder context)".into())
239 })?;
240
241 // Find the private key by label.
242 let template = vec![
243 Attribute::Label(key_label.as_bytes().to_vec()),
244 Attribute::Class(ObjectClass::PRIVATE_KEY),
245 ];
246
247 let objects = session
248 .find_objects(&template)
249 .map_err(|e| HsmError::KeyNotFound(format!("Failed to find key '{key_label}': {e}")))?;
250
251 let private_key = objects.into_iter().next().ok_or_else(|| {
252 HsmError::KeyNotFound(format!("Private key '{key_label}' not found in token"))
253 })?;
254
255 // Select the combined hash+sign mechanism based on algorithm.
256 // These mechanisms hash the data internally then sign.
257 let mechanism = match hash_algorithm {
258 "sha256" => Mechanism::Sha256RsaPkcs,
259 "sha384" => Mechanism::Sha384RsaPkcs,
260 "sha512" => Mechanism::Sha512RsaPkcs,
261 other => {
262 return Err(HsmError::UnsupportedMechanism(format!(
263 "Unsupported hash algorithm for RSA signing: {other}"
264 )));
265 }
266 };
267
268 // Sign the data.
269 session.sign(&mechanism, private_key, data).map_err(|e| {
270 HsmError::SigningFailure(format!("C_Sign failed for key '{key_label}': {e}"))
271 })
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_module_structure() {
281 // Smoke test to ensure all modules compile and link
282 let _provider = HsmProvider::Kryoptic;
283 let _config = _provider.config();
284 assert!(!_config.library_path.is_empty());
285 }
286
287 #[test]
288 fn test_pqc_mechanism_ids() {
289 let ids = PqcMechanismIds::default();
290 assert!(ids.ml_dsa_keygen.is_some());
291 assert!(ids.ml_kem_keygen.is_some());
292 }
293
294 #[test]
295 fn test_key_algorithms() {
296 let rsa = KeyAlgorithm::Rsa(2048);
297 let ecdsa = KeyAlgorithm::Ecdsa(EcdsaCurve::P256);
298 let ml_dsa = KeyAlgorithm::MlDsa(MlDsaLevel::L3);
299 let ml_kem = KeyAlgorithm::MlKem(MlKemLevel::L3);
300
301 // Just verify they construct
302 assert!(matches!(rsa, KeyAlgorithm::Rsa(2048)));
303 assert!(matches!(ecdsa, KeyAlgorithm::Ecdsa(_)));
304 assert!(matches!(ml_dsa, KeyAlgorithm::MlDsa(_)));
305 assert!(matches!(ml_kem, KeyAlgorithm::MlKem(_)));
306 }
307}