Skip to main content

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}