Skip to main content

kipuka_hsm/
sign.rs

1//! HSM signing operations with PQC support.
2
3use crate::error::{HsmError, HsmResult};
4use crate::key::{HsmKeyPair, KeyAlgorithm, MlDsaLevel, MlKemLevel, PqcMechanismIds};
5use cryptoki::mechanism::{Mechanism, MechanismType};
6use cryptoki::object::ObjectHandle;
7use cryptoki::session::Session;
8
9/// HSM signer trait.
10pub trait HsmSigner {
11    /// Sign a message digest.
12    ///
13    /// # Arguments
14    ///
15    /// * `key` - Key pair to sign with
16    /// * `digest` - Pre-computed message digest
17    ///
18    /// # Returns
19    ///
20    /// The signature bytes.
21    fn sign(&self, key: &HsmKeyPair, digest: &[u8]) -> HsmResult<Vec<u8>>;
22
23    /// Sign a message digest with a specific mechanism.
24    fn sign_with_mechanism(
25        &self,
26        key: &HsmKeyPair,
27        digest: &[u8],
28        mechanism: &Mechanism,
29    ) -> HsmResult<Vec<u8>>;
30
31    /// Wrap a key using AES Key Wrap (RFC 3394).
32    ///
33    /// Used for wrapping ML-KEM private keys during /serverkeygen.
34    fn wrap_key_aes(
35        &self,
36        session: &Session,
37        wrapping_key: ObjectHandle,
38        key_to_wrap: ObjectHandle,
39    ) -> HsmResult<Vec<u8>>;
40
41    /// Wrap a key using RSAES-OAEP.
42    fn wrap_key_rsa_oaep(
43        &self,
44        session: &Session,
45        wrapping_key: ObjectHandle,
46        key_to_wrap: ObjectHandle,
47    ) -> HsmResult<Vec<u8>>;
48
49    /// ML-KEM encapsulate operation.
50    ///
51    /// # Arguments
52    ///
53    /// * `session` - PKCS#11 session
54    /// * `public_key` - ML-KEM public key
55    ///
56    /// # Returns
57    ///
58    /// `(ciphertext, shared_secret)` tuple.
59    fn ml_kem_encapsulate(
60        &self,
61        session: &Session,
62        public_key: ObjectHandle,
63        pqc_mechanisms: &PqcMechanismIds,
64    ) -> HsmResult<(Vec<u8>, Vec<u8>)>;
65
66    /// ML-KEM decapsulate operation.
67    ///
68    /// # Arguments
69    ///
70    /// * `session` - PKCS#11 session
71    /// * `private_key` - ML-KEM private key
72    /// * `ciphertext` - Ciphertext from encapsulate
73    ///
74    /// # Returns
75    ///
76    /// The shared secret.
77    fn ml_kem_decapsulate(
78        &self,
79        session: &Session,
80        private_key: ObjectHandle,
81        ciphertext: &[u8],
82        pqc_mechanisms: &PqcMechanismIds,
83    ) -> HsmResult<Vec<u8>>;
84}
85
86/// Default HSM signer implementation.
87pub struct DefaultHsmSigner;
88
89impl HsmSigner for DefaultHsmSigner {
90    fn sign(&self, key: &HsmKeyPair, digest: &[u8]) -> HsmResult<Vec<u8>> {
91        let mechanism = match key.algorithm() {
92            KeyAlgorithm::Rsa(_bits) => {
93                // Default to PKCS#1 v1.5 with SHA-256
94                Mechanism::Sha256RsaPkcs
95            }
96            KeyAlgorithm::Ecdsa(_curve) => {
97                // ECDSA with pre-computed digest
98                Mechanism::Ecdsa
99            }
100            KeyAlgorithm::MlDsa(_level) => {
101                // ML-DSA requires vendor-specific mechanism
102                return Err(HsmError::PqcNotSupported(
103                    "ML-DSA signing requires explicit mechanism ID".to_string(),
104                ));
105            }
106            KeyAlgorithm::MlKem(_level) => {
107                return Err(HsmError::UnsupportedMechanism(
108                    "ML-KEM is for encapsulation, not signing".to_string(),
109                ));
110            }
111        };
112
113        self.sign_with_mechanism(key, digest, &mechanism)
114    }
115
116    fn sign_with_mechanism(
117        &self,
118        key: &HsmKeyPair,
119        digest: &[u8],
120        mechanism: &Mechanism,
121    ) -> HsmResult<Vec<u8>> {
122        let session = key.session();
123        let private_key = key.private_key();
124
125        session
126            .sign(mechanism, private_key, digest)
127            .map_err(|e| HsmError::SigningFailure(format!("Sign operation failed: {e}")))
128    }
129
130    fn wrap_key_aes(
131        &self,
132        session: &Session,
133        wrapping_key: ObjectHandle,
134        key_to_wrap: ObjectHandle,
135    ) -> HsmResult<Vec<u8>> {
136        let mechanism = Mechanism::AesKeyWrap;
137
138        session
139            .wrap_key(&mechanism, wrapping_key, key_to_wrap)
140            .map_err(|e| HsmError::KeyWrap(format!("AES key wrap failed: {e}")))
141    }
142
143    fn wrap_key_rsa_oaep(
144        &self,
145        _session: &Session,
146        _wrapping_key: ObjectHandle,
147        _key_to_wrap: ObjectHandle,
148    ) -> HsmResult<Vec<u8>> {
149        // RSA-OAEP requires explicit parameters in cryptoki 0.7
150        // Placeholder for future implementation
151        Err(HsmError::KeyWrap(
152            "RSA-OAEP key wrap not yet implemented for cryptoki 0.7".to_string(),
153        ))
154    }
155
156    fn ml_kem_encapsulate(
157        &self,
158        _session: &Session,
159        _public_key: ObjectHandle,
160        _pqc_mechanisms: &PqcMechanismIds,
161    ) -> HsmResult<(Vec<u8>, Vec<u8>)> {
162        // ML-KEM encapsulation is not directly supported via standard PKCS#11 operations
163        // This would require vendor-specific extensions or software fallback
164        Err(HsmError::PqcNotSupported(
165            "ML-KEM encapsulate not yet implemented (requires vendor extensions)".to_string(),
166        ))
167    }
168
169    fn ml_kem_decapsulate(
170        &self,
171        _session: &Session,
172        _private_key: ObjectHandle,
173        _ciphertext: &[u8],
174        _pqc_mechanisms: &PqcMechanismIds,
175    ) -> HsmResult<Vec<u8>> {
176        // ML-KEM decapsulation is not directly supported via standard PKCS#11 operations
177        Err(HsmError::PqcNotSupported(
178            "ML-KEM decapsulate not yet implemented (requires vendor extensions)".to_string(),
179        ))
180    }
181}
182
183/// Sign with RSA PKCS#1 v1.5.
184pub fn sign_rsa_pkcs1(
185    key: &HsmKeyPair,
186    digest: &[u8],
187    hash_algorithm: RsaHashAlgorithm,
188) -> HsmResult<Vec<u8>> {
189    let mechanism = match hash_algorithm {
190        RsaHashAlgorithm::Sha256 => Mechanism::Sha256RsaPkcs,
191        RsaHashAlgorithm::Sha384 => Mechanism::Sha384RsaPkcs,
192        RsaHashAlgorithm::Sha512 => Mechanism::Sha512RsaPkcs,
193    };
194
195    let signer = DefaultHsmSigner;
196    signer.sign_with_mechanism(key, digest, &mechanism)
197}
198
199/// Sign with RSA-PSS.
200pub fn sign_rsa_pss(
201    _key: &HsmKeyPair,
202    _digest: &[u8],
203    _hash_algorithm: RsaHashAlgorithm,
204) -> HsmResult<Vec<u8>> {
205    // RSA-PSS requires explicit parameters in cryptoki 0.7
206    // Placeholder for future implementation
207    Err(HsmError::UnsupportedMechanism(
208        "RSA-PSS signing not yet implemented for cryptoki 0.7".to_string(),
209    ))
210}
211
212/// Sign with ECDSA.
213pub fn sign_ecdsa(key: &HsmKeyPair, digest: &[u8]) -> HsmResult<Vec<u8>> {
214    let mechanism = Mechanism::Ecdsa;
215    let signer = DefaultHsmSigner;
216    signer.sign_with_mechanism(key, digest, &mechanism)
217}
218
219/// Sign with ML-DSA (FIPS 204).
220///
221/// ML-DSA signing internally hashes the message before signing.
222/// The HSM performs both hashing and signing.
223pub fn sign_ml_dsa(
224    _key: &HsmKeyPair,
225    _message: &[u8],
226    level: MlDsaLevel,
227    pqc_mechanisms: &PqcMechanismIds,
228) -> HsmResult<Vec<u8>> {
229    let mechanism_id = match level {
230        MlDsaLevel::L2 => pqc_mechanisms.ml_dsa_44,
231        MlDsaLevel::L3 => pqc_mechanisms.ml_dsa_65,
232        MlDsaLevel::L5 => pqc_mechanisms.ml_dsa_87,
233    }
234    .ok_or_else(|| {
235        HsmError::PqcNotSupported(format!("ML-DSA level {level:?} mechanism not configured"))
236    })?;
237
238    tracing::warn!(
239        "Attempting ML-DSA signing with vendor mechanism ID 0x{:08x}",
240        mechanism_id
241    );
242
243    // cryptoki 0.7 doesn't support vendor-defined mechanisms directly
244    // Fall back to error for now - HSM support requires vendor SDK integration
245    Err(HsmError::PqcNotSupported(
246        "ML-DSA signing requires vendor-specific PKCS#11 extensions not available in cryptoki 0.7"
247            .to_string(),
248    ))
249}
250
251/// RSA hash algorithms.
252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum RsaHashAlgorithm {
254    Sha256,
255    Sha384,
256    Sha512,
257}
258
259/// Software fallback for PQC operations.
260///
261/// When HSM does not support ML-DSA or ML-KEM, fall back to synta-certificate
262/// software implementations.
263pub struct SoftwarePqcFallback;
264
265impl SoftwarePqcFallback {
266    /// Check if HSM supports the required PQC mechanism.
267    pub fn is_hsm_supported(
268        mechanism_type: MechanismType,
269        provider_mechanisms: &[MechanismType],
270    ) -> bool {
271        provider_mechanisms.contains(&mechanism_type)
272    }
273
274    /// Sign with ML-DSA using software implementation.
275    ///
276    /// This is a placeholder - actual implementation would use synta-certificate.
277    pub fn sign_ml_dsa_software(
278        _message: &[u8],
279        _private_key_bytes: &[u8],
280        _level: MlDsaLevel,
281    ) -> HsmResult<Vec<u8>> {
282        // Would use synta-certificate ML-DSA implementation
283        Err(HsmError::PqcNotSupported(
284            "Software ML-DSA fallback not yet implemented".to_string(),
285        ))
286    }
287
288    /// ML-KEM encapsulate using software implementation.
289    pub fn ml_kem_encapsulate_software(
290        _public_key_bytes: &[u8],
291        _level: MlKemLevel,
292    ) -> HsmResult<(Vec<u8>, Vec<u8>)> {
293        // Would use synta-certificate ML-KEM implementation
294        Err(HsmError::PqcNotSupported(
295            "Software ML-KEM encapsulate fallback not yet implemented".to_string(),
296        ))
297    }
298
299    /// ML-KEM decapsulate using software implementation.
300    pub fn ml_kem_decapsulate_software(
301        _private_key_bytes: &[u8],
302        _ciphertext: &[u8],
303        _level: MlKemLevel,
304    ) -> HsmResult<Vec<u8>> {
305        // Would use synta-certificate ML-KEM implementation
306        Err(HsmError::PqcNotSupported(
307            "Software ML-KEM decapsulate fallback not yet implemented".to_string(),
308        ))
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    #[test]
317    fn test_software_fallback_detection() {
318        let mechanisms = vec![
319            MechanismType::RSA_PKCS,
320            MechanismType::ECDSA,
321            MechanismType::AES_KEY_WRAP,
322        ];
323
324        assert!(SoftwarePqcFallback::is_hsm_supported(
325            MechanismType::RSA_PKCS,
326            &mechanisms
327        ));
328        assert!(!SoftwarePqcFallback::is_hsm_supported(
329            MechanismType::ECDSA_SHA256,
330            &mechanisms
331        ));
332    }
333}