1use crate::error::{HsmError, HsmResult};
4use crate::key::{HsmKeyPair, KeyAlgorithm, MlDsaLevel, MlKemLevel};
5use cryptoki::mechanism::dsa::{HedgeType, SignAdditionalContext};
6use cryptoki::mechanism::rsa::{PkcsMgfType, PkcsOaepParams, PkcsOaepSource, PkcsPssParams};
7use cryptoki::mechanism::{Mechanism, MechanismType};
8use cryptoki::object::{Attribute, ObjectHandle};
9use cryptoki::session::Session;
10use cryptoki::types::Ulong;
11
12pub trait HsmSigner {
14 fn sign(&self, key: &HsmKeyPair, digest: &[u8]) -> HsmResult<Vec<u8>>;
25
26 fn sign_with_mechanism(
28 &self,
29 key: &HsmKeyPair,
30 digest: &[u8],
31 mechanism: &Mechanism,
32 ) -> HsmResult<Vec<u8>>;
33
34 fn wrap_key_aes(
38 &self,
39 session: &Session,
40 wrapping_key: ObjectHandle,
41 key_to_wrap: ObjectHandle,
42 ) -> HsmResult<Vec<u8>>;
43
44 fn wrap_key_rsa_oaep(
46 &self,
47 session: &Session,
48 wrapping_key: ObjectHandle,
49 key_to_wrap: ObjectHandle,
50 ) -> HsmResult<Vec<u8>>;
51
52 fn ml_kem_encapsulate(
65 &self,
66 session: &Session,
67 public_key: ObjectHandle,
68 ) -> HsmResult<(Vec<u8>, Vec<u8>)>;
69
70 fn ml_kem_decapsulate(
82 &self,
83 session: &Session,
84 private_key: ObjectHandle,
85 ciphertext: &[u8],
86 ) -> HsmResult<Vec<u8>>;
87}
88
89pub struct DefaultHsmSigner;
91
92impl HsmSigner for DefaultHsmSigner {
93 fn sign(&self, key: &HsmKeyPair, digest: &[u8]) -> HsmResult<Vec<u8>> {
94 let mechanism = match key.algorithm() {
95 KeyAlgorithm::Rsa(_bits) => {
96 Mechanism::Sha256RsaPkcs
98 }
99 KeyAlgorithm::Ecdsa(_curve) => {
100 Mechanism::Ecdsa
102 }
103 KeyAlgorithm::MlDsa(_level) => {
104 return Err(HsmError::PqcNotSupported(
106 "ML-DSA signing requires explicit mechanism ID".to_string(),
107 ));
108 }
109 KeyAlgorithm::MlKem(_level) => {
110 return Err(HsmError::UnsupportedMechanism(
111 "ML-KEM is for encapsulation, not signing".to_string(),
112 ));
113 }
114 };
115
116 self.sign_with_mechanism(key, digest, &mechanism)
117 }
118
119 fn sign_with_mechanism(
120 &self,
121 key: &HsmKeyPair,
122 digest: &[u8],
123 mechanism: &Mechanism,
124 ) -> HsmResult<Vec<u8>> {
125 let session = key.session();
126 let private_key = key.private_key();
127
128 session
129 .sign(mechanism, private_key, digest)
130 .map_err(|e| HsmError::SigningFailure(format!("Sign operation failed: {e}")))
131 }
132
133 fn wrap_key_aes(
134 &self,
135 session: &Session,
136 wrapping_key: ObjectHandle,
137 key_to_wrap: ObjectHandle,
138 ) -> HsmResult<Vec<u8>> {
139 let mechanism = Mechanism::AesKeyWrap;
140
141 session
142 .wrap_key(&mechanism, wrapping_key, key_to_wrap)
143 .map_err(|e| HsmError::KeyWrap(format!("AES key wrap failed: {e}")))
144 }
145
146 fn wrap_key_rsa_oaep(
147 &self,
148 session: &Session,
149 wrapping_key: ObjectHandle,
150 key_to_wrap: ObjectHandle,
151 ) -> HsmResult<Vec<u8>> {
152 let oaep_params = PkcsOaepParams::new(
154 MechanismType::SHA256,
155 PkcsMgfType::MGF1_SHA256,
156 PkcsOaepSource::empty(),
157 );
158 let mechanism = Mechanism::RsaPkcsOaep(oaep_params);
159
160 session
161 .wrap_key(&mechanism, wrapping_key, key_to_wrap)
162 .map_err(|e| HsmError::KeyWrap(format!("RSA-OAEP key wrap failed: {e}")))
163 }
164
165 fn ml_kem_encapsulate(
166 &self,
167 session: &Session,
168 public_key: ObjectHandle,
169 ) -> HsmResult<(Vec<u8>, Vec<u8>)> {
170 let mechanism = Mechanism::MlKem;
175
176 let template = vec![
178 Attribute::Token(false), Attribute::Extractable(true), Attribute::Sensitive(false), ];
182
183 let (ciphertext, secret_handle) = session
184 .encapsulate_key(&mechanism, public_key, &template)
185 .map_err(|e| HsmError::PqcNotSupported(format!("ML-KEM encapsulate failed: {e}")))?;
186
187 let attrs = match session
190 .get_attributes(secret_handle, &[cryptoki::object::AttributeType::Value])
191 {
192 Ok(a) => a,
193 Err(e) => {
194 if let Err(de) = session.destroy_object(secret_handle) {
195 tracing::warn!(error = %de, "failed to destroy temporary KEM shared secret");
196 }
197 return Err(HsmError::PqcNotSupported(format!(
198 "ML-KEM: failed to extract shared secret: {e}"
199 )));
200 }
201 };
202
203 let shared_secret = match attrs.into_iter().find_map(|a| {
204 if let Attribute::Value(v) = a {
205 Some(v)
206 } else {
207 None
208 }
209 }) {
210 Some(v) => v,
211 None => {
212 if let Err(de) = session.destroy_object(secret_handle) {
213 tracing::warn!(error = %de, "failed to destroy temporary KEM shared secret");
214 }
215 return Err(HsmError::PqcNotSupported(
216 "ML-KEM: derived key has no CKA_VALUE attribute".to_string(),
217 ));
218 }
219 };
220
221 if let Err(e) = session.destroy_object(secret_handle) {
223 tracing::warn!(error = %e, "failed to destroy temporary KEM shared secret");
224 }
225
226 Ok((ciphertext, shared_secret))
227 }
228
229 fn ml_kem_decapsulate(
230 &self,
231 session: &Session,
232 private_key: ObjectHandle,
233 ciphertext: &[u8],
234 ) -> HsmResult<Vec<u8>> {
235 let mechanism = Mechanism::MlKem;
239
240 let template = vec![
242 Attribute::Token(false),
243 Attribute::Extractable(true),
244 Attribute::Sensitive(false),
245 ];
246
247 let secret_handle = session
248 .decapsulate_key(&mechanism, private_key, &template, ciphertext)
249 .map_err(|e| HsmError::PqcNotSupported(format!("ML-KEM decapsulate failed: {e}")))?;
250
251 let attrs = match session
253 .get_attributes(secret_handle, &[cryptoki::object::AttributeType::Value])
254 {
255 Ok(a) => a,
256 Err(e) => {
257 if let Err(de) = session.destroy_object(secret_handle) {
258 tracing::warn!(error = %de, "failed to destroy temporary KEM shared secret");
259 }
260 return Err(HsmError::PqcNotSupported(format!(
261 "ML-KEM: failed to extract shared secret: {e}"
262 )));
263 }
264 };
265
266 let shared_secret = match attrs.into_iter().find_map(|a| {
267 if let Attribute::Value(v) = a {
268 Some(v)
269 } else {
270 None
271 }
272 }) {
273 Some(v) => v,
274 None => {
275 if let Err(de) = session.destroy_object(secret_handle) {
276 tracing::warn!(error = %de, "failed to destroy temporary KEM shared secret");
277 }
278 return Err(HsmError::PqcNotSupported(
279 "ML-KEM: derived key has no CKA_VALUE attribute".to_string(),
280 ));
281 }
282 };
283
284 if let Err(e) = session.destroy_object(secret_handle) {
286 tracing::warn!(error = %e, "failed to destroy temporary KEM shared secret");
287 }
288
289 Ok(shared_secret)
290 }
291}
292
293pub fn sign_rsa_pkcs1(
295 key: &HsmKeyPair,
296 digest: &[u8],
297 hash_algorithm: RsaHashAlgorithm,
298) -> HsmResult<Vec<u8>> {
299 let mechanism = match hash_algorithm {
300 RsaHashAlgorithm::Sha256 => Mechanism::Sha256RsaPkcs,
301 RsaHashAlgorithm::Sha384 => Mechanism::Sha384RsaPkcs,
302 RsaHashAlgorithm::Sha512 => Mechanism::Sha512RsaPkcs,
303 };
304
305 let signer = DefaultHsmSigner;
306 signer.sign_with_mechanism(key, digest, &mechanism)
307}
308
309pub fn sign_rsa_pss(
318 key: &HsmKeyPair,
319 digest: &[u8],
320 hash_algorithm: RsaHashAlgorithm,
321) -> HsmResult<Vec<u8>> {
322 let (hash_mech, mgf, salt_len) = match hash_algorithm {
323 RsaHashAlgorithm::Sha256 => (MechanismType::SHA256, PkcsMgfType::MGF1_SHA256, 32u64),
324 RsaHashAlgorithm::Sha384 => (MechanismType::SHA384, PkcsMgfType::MGF1_SHA384, 48u64),
325 RsaHashAlgorithm::Sha512 => (MechanismType::SHA512, PkcsMgfType::MGF1_SHA512, 64u64),
326 };
327
328 let pss_params = PkcsPssParams {
329 hash_alg: hash_mech,
330 mgf,
331 s_len: Ulong::from(salt_len),
332 };
333
334 let mechanism = match hash_algorithm {
335 RsaHashAlgorithm::Sha256 => Mechanism::Sha256RsaPkcsPss(pss_params),
336 RsaHashAlgorithm::Sha384 => Mechanism::Sha384RsaPkcsPss(pss_params),
337 RsaHashAlgorithm::Sha512 => Mechanism::Sha512RsaPkcsPss(pss_params),
338 };
339
340 let signer = DefaultHsmSigner;
341 signer.sign_with_mechanism(key, digest, &mechanism)
342}
343
344pub fn sign_ecdsa(key: &HsmKeyPair, digest: &[u8]) -> HsmResult<Vec<u8>> {
346 let mechanism = Mechanism::Ecdsa;
347 let signer = DefaultHsmSigner;
348 signer.sign_with_mechanism(key, digest, &mechanism)
349}
350
351pub fn sign_ml_dsa(key: &HsmKeyPair, message: &[u8], _level: MlDsaLevel) -> HsmResult<Vec<u8>> {
357 let ctx = SignAdditionalContext::new(HedgeType::Preferred, None);
361 let mechanism = Mechanism::MlDsa(ctx);
362
363 let signer = DefaultHsmSigner;
364 signer.sign_with_mechanism(key, message, &mechanism)
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum RsaHashAlgorithm {
370 Sha256,
371 Sha384,
372 Sha512,
373}
374
375pub struct SoftwarePqcFallback;
380
381impl SoftwarePqcFallback {
382 pub fn is_hsm_supported(
384 mechanism_type: MechanismType,
385 provider_mechanisms: &[MechanismType],
386 ) -> bool {
387 provider_mechanisms.contains(&mechanism_type)
388 }
389
390 pub fn sign_ml_dsa_software(
395 _message: &[u8],
396 _private_key_bytes: &[u8],
397 _level: MlDsaLevel,
398 ) -> HsmResult<Vec<u8>> {
399 Err(HsmError::PqcNotSupported(
409 "Software ML-DSA fallback requires synta-certificate 'pqc' feature. \
410 Use BackendPrivateKey::generate_ml_dsa() to create and sign."
411 .to_string(),
412 ))
413 }
414
415 pub fn ml_kem_encapsulate_software(
420 _public_key_bytes: &[u8],
421 _level: MlKemLevel,
422 ) -> HsmResult<(Vec<u8>, Vec<u8>)> {
423 Err(HsmError::PqcNotSupported(
432 "Software ML-KEM encapsulate fallback requires synta-certificate 'pqc' feature. \
433 Use BackendPrivateKey::generate_ml_kem() to create keys, then encapsulate."
434 .to_string(),
435 ))
436 }
437
438 pub fn ml_kem_decapsulate_software(
443 _private_key_bytes: &[u8],
444 _ciphertext: &[u8],
445 _level: MlKemLevel,
446 ) -> HsmResult<Vec<u8>> {
447 Err(HsmError::PqcNotSupported(
456 "Software ML-KEM decapsulate fallback requires synta-certificate 'pqc' feature. \
457 Use BackendPrivateKey::generate_ml_kem() to create keys, then decapsulate."
458 .to_string(),
459 ))
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn test_software_fallback_detection() {
469 let mechanisms = vec![
470 MechanismType::RSA_PKCS,
471 MechanismType::ECDSA,
472 MechanismType::AES_KEY_WRAP,
473 ];
474
475 assert!(SoftwarePqcFallback::is_hsm_supported(
476 MechanismType::RSA_PKCS,
477 &mechanisms
478 ));
479 assert!(!SoftwarePqcFallback::is_hsm_supported(
480 MechanismType::ECDSA_SHA256,
481 &mechanisms
482 ));
483 }
484
485 #[test]
486 fn test_pqc_mechanism_detection() {
487 let mechanisms = vec![
489 MechanismType::RSA_PKCS,
490 MechanismType::ML_DSA_KEY_PAIR_GEN,
491 MechanismType::ML_DSA,
492 MechanismType::ML_KEM_KEY_PAIR_GEN,
493 MechanismType::ML_KEM,
494 ];
495
496 assert!(SoftwarePqcFallback::is_hsm_supported(
497 MechanismType::ML_DSA,
498 &mechanisms
499 ));
500 assert!(SoftwarePqcFallback::is_hsm_supported(
501 MechanismType::ML_KEM,
502 &mechanisms
503 ));
504 assert!(!SoftwarePqcFallback::is_hsm_supported(
505 MechanismType::HASH_ML_DSA_SHA256,
506 &mechanisms
507 ));
508 }
509
510 #[test]
511 fn test_software_fallback_errors_are_descriptive() {
512 let err = SoftwarePqcFallback::sign_ml_dsa_software(&[], &[], MlDsaLevel::L3);
513 assert!(err.is_err());
514 let msg = err.unwrap_err().to_string();
515 assert!(msg.contains("synta-certificate"));
516
517 let err = SoftwarePqcFallback::ml_kem_encapsulate_software(&[], MlKemLevel::L3);
518 assert!(err.is_err());
519 let msg = err.unwrap_err().to_string();
520 assert!(msg.contains("synta-certificate"));
521
522 let err = SoftwarePqcFallback::ml_kem_decapsulate_software(&[], &[], MlKemLevel::L3);
523 assert!(err.is_err());
524 let msg = err.unwrap_err().to_string();
525 assert!(msg.contains("synta-certificate"));
526 }
527}