1use crate::error::{HsmError, HsmResult};
4use crate::providers::HsmProviderConfig;
5use crate::slot::HsmSlot;
6use cryptoki::mechanism::{Mechanism, MechanismType};
7use cryptoki::object::{
8 Attribute, KeyType, MlDsaParameterSetType, MlKemParameterSetType, ObjectClass, ObjectHandle,
9};
10use cryptoki::session::Session;
11use cryptoki::types::Ulong;
12use std::collections::HashMap;
13use url::Url;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum KeyAlgorithm {
18 Rsa(u32),
20 Ecdsa(EcdsaCurve),
22 MlDsa(MlDsaLevel),
24 MlKem(MlKemLevel),
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum EcdsaCurve {
31 P256,
32 P384,
33 P521,
34}
35
36impl EcdsaCurve {
37 pub fn oid(&self) -> &[u8] {
39 match self {
40 Self::P256 => &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07],
41 Self::P384 => &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22],
42 Self::P521 => &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23],
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum MlDsaLevel {
50 L2,
52 L3,
54 L5,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum MlKemLevel {
61 L1,
63 L3,
65 L5,
67}
68
69pub struct HsmKeyPair {
71 session: Session,
73 private_key: ObjectHandle,
75 public_key: ObjectHandle,
77 algorithm: KeyAlgorithm,
79}
80
81impl HsmKeyPair {
82 pub fn generate(
103 slot: &HsmSlot,
104 algorithm: KeyAlgorithm,
105 label: &str,
106 id: &[u8],
107 provider_config: &HsmProviderConfig,
108 ) -> HsmResult<Self> {
109 let session = slot.open_rw_session()?;
110
111 let (public_key, private_key) = match algorithm {
112 KeyAlgorithm::Rsa(bits) => {
113 Self::generate_rsa(&session, bits, label, id, provider_config)?
114 }
115 KeyAlgorithm::Ecdsa(curve) => {
116 Self::generate_ecdsa(&session, curve, label, id, provider_config)?
117 }
118 KeyAlgorithm::MlDsa(level) => {
119 Self::generate_ml_dsa(&session, level, label, id, provider_config)?
120 }
121 KeyAlgorithm::MlKem(level) => {
122 Self::generate_ml_kem(&session, level, label, id, provider_config)?
123 }
124 };
125
126 Ok(Self {
127 session,
128 private_key,
129 public_key,
130 algorithm,
131 })
132 }
133
134 fn generate_rsa(
136 session: &Session,
137 bits: u32,
138 label: &str,
139 id: &[u8],
140 config: &HsmProviderConfig,
141 ) -> HsmResult<(ObjectHandle, ObjectHandle)> {
142 if !config
143 .supported_mechanisms
144 .contains(&MechanismType::RSA_PKCS_KEY_PAIR_GEN)
145 {
146 return Err(HsmError::UnsupportedMechanism(
147 "RSA key generation not supported by HSM".to_string(),
148 ));
149 }
150
151 let mechanism = Mechanism::RsaPkcsKeyPairGen;
152
153 let public_key_template = vec![
154 Attribute::Token(true),
155 Attribute::Label(label.as_bytes().to_vec()),
156 Attribute::Id(id.to_vec()),
157 Attribute::Encrypt(true),
158 Attribute::Verify(true),
159 Attribute::ModulusBits(Ulong::from(bits as u64)),
160 Attribute::PublicExponent(vec![0x01, 0x00, 0x01]), ];
162
163 let private_key_template = vec![
164 Attribute::Token(true),
165 Attribute::Label(label.as_bytes().to_vec()),
166 Attribute::Id(id.to_vec()),
167 Attribute::Private(true),
168 Attribute::Sensitive(true), Attribute::Extractable(false), Attribute::Decrypt(true),
171 Attribute::Sign(true),
172 ];
173
174 session
175 .generate_key_pair(&mechanism, &public_key_template, &private_key_template)
176 .map_err(|e| HsmError::KeyGeneration(format!("RSA key generation failed: {e}")))
177 }
178
179 fn generate_ecdsa(
181 session: &Session,
182 curve: EcdsaCurve,
183 label: &str,
184 id: &[u8],
185 config: &HsmProviderConfig,
186 ) -> HsmResult<(ObjectHandle, ObjectHandle)> {
187 if !config
188 .supported_mechanisms
189 .contains(&MechanismType::ECC_KEY_PAIR_GEN)
190 {
191 return Err(HsmError::UnsupportedMechanism(
192 "ECDSA key generation not supported by HSM".to_string(),
193 ));
194 }
195
196 let mechanism = Mechanism::EccKeyPairGen;
197
198 let public_key_template = vec![
199 Attribute::Token(true),
200 Attribute::Label(label.as_bytes().to_vec()),
201 Attribute::Id(id.to_vec()),
202 Attribute::Verify(true),
203 Attribute::EcParams(curve.oid().to_vec()),
204 ];
205
206 let private_key_template = vec![
207 Attribute::Token(true),
208 Attribute::Label(label.as_bytes().to_vec()),
209 Attribute::Id(id.to_vec()),
210 Attribute::Private(true),
211 Attribute::Sensitive(true), Attribute::Extractable(false), Attribute::Sign(true),
214 ];
215
216 session
217 .generate_key_pair(&mechanism, &public_key_template, &private_key_template)
218 .map_err(|e| HsmError::KeyGeneration(format!("ECDSA key generation failed: {e}")))
219 }
220
221 fn generate_ml_dsa(
226 session: &Session,
227 level: MlDsaLevel,
228 label: &str,
229 id: &[u8],
230 config: &HsmProviderConfig,
231 ) -> HsmResult<(ObjectHandle, ObjectHandle)> {
232 if !config
234 .supported_mechanisms
235 .contains(&MechanismType::ML_DSA_KEY_PAIR_GEN)
236 {
237 return Err(HsmError::PqcNotSupported(
238 "ML-DSA key generation (CKM_ML_DSA_KEY_PAIR_GEN) not supported by HSM. \
239 Consider using SoftwarePqcFallback."
240 .to_string(),
241 ));
242 }
243
244 let mechanism = Mechanism::MlDsaKeyPairGen;
245
246 let param_set = match level {
248 MlDsaLevel::L2 => MlDsaParameterSetType::ML_DSA_44,
249 MlDsaLevel::L3 => MlDsaParameterSetType::ML_DSA_65,
250 MlDsaLevel::L5 => MlDsaParameterSetType::ML_DSA_87,
251 };
252
253 let public_key_template = vec![
254 Attribute::Token(true),
255 Attribute::Label(label.as_bytes().to_vec()),
256 Attribute::Id(id.to_vec()),
257 Attribute::Verify(true),
258 Attribute::KeyType(KeyType::ML_DSA),
259 Attribute::ParameterSet(param_set.into()),
260 ];
261
262 let private_key_template = vec![
263 Attribute::Token(true),
264 Attribute::Label(label.as_bytes().to_vec()),
265 Attribute::Id(id.to_vec()),
266 Attribute::Private(true),
267 Attribute::Sensitive(true), Attribute::Extractable(false), Attribute::Sign(true),
270 Attribute::KeyType(KeyType::ML_DSA),
271 Attribute::ParameterSet(param_set.into()),
272 ];
273
274 tracing::info!("Generating ML-DSA key pair with parameter set {:?}", level);
275
276 session
277 .generate_key_pair(&mechanism, &public_key_template, &private_key_template)
278 .map_err(|e| HsmError::KeyGeneration(format!("ML-DSA key generation failed: {e}")))
279 }
280
281 fn generate_ml_kem(
286 session: &Session,
287 level: MlKemLevel,
288 label: &str,
289 id: &[u8],
290 config: &HsmProviderConfig,
291 ) -> HsmResult<(ObjectHandle, ObjectHandle)> {
292 if !config
294 .supported_mechanisms
295 .contains(&MechanismType::ML_KEM_KEY_PAIR_GEN)
296 {
297 return Err(HsmError::PqcNotSupported(
298 "ML-KEM key generation (CKM_ML_KEM_KEY_PAIR_GEN) not supported by HSM. \
299 Consider using SoftwarePqcFallback."
300 .to_string(),
301 ));
302 }
303
304 let mechanism = Mechanism::MlKemKeyPairGen;
305
306 let param_set = match level {
308 MlKemLevel::L1 => MlKemParameterSetType::ML_KEM_512,
309 MlKemLevel::L3 => MlKemParameterSetType::ML_KEM_768,
310 MlKemLevel::L5 => MlKemParameterSetType::ML_KEM_1024,
311 };
312
313 let public_key_template = vec![
314 Attribute::Token(true),
315 Attribute::Label(label.as_bytes().to_vec()),
316 Attribute::Id(id.to_vec()),
317 Attribute::KeyType(KeyType::ML_KEM),
318 Attribute::ParameterSet(param_set.into()),
319 ];
320
321 let private_key_template = vec![
322 Attribute::Token(true),
323 Attribute::Label(label.as_bytes().to_vec()),
324 Attribute::Id(id.to_vec()),
325 Attribute::Private(true),
326 Attribute::Sensitive(true), Attribute::Extractable(false), Attribute::KeyType(KeyType::ML_KEM),
329 Attribute::ParameterSet(param_set.into()),
330 ];
331
332 tracing::info!("Generating ML-KEM key pair with parameter set {:?}", level);
333
334 session
335 .generate_key_pair(&mechanism, &public_key_template, &private_key_template)
336 .map_err(|e| HsmError::KeyGeneration(format!("ML-KEM key generation failed: {e}")))
337 }
338
339 pub fn find_by_label(slot: &HsmSlot, label: &str, algorithm: KeyAlgorithm) -> HsmResult<Self> {
341 Self::find_selected(
342 slot,
343 vec![Attribute::Label(label.as_bytes().to_vec())],
344 algorithm,
345 )
346 }
347
348 pub fn find_by_id(slot: &HsmSlot, id: &[u8], algorithm: KeyAlgorithm) -> HsmResult<Self> {
349 Self::find_selected(slot, vec![Attribute::Id(id.to_vec())], algorithm)
350 }
351
352 fn find_selected(
353 slot: &HsmSlot,
354 selector: Vec<Attribute>,
355 algorithm: KeyAlgorithm,
356 ) -> HsmResult<Self> {
357 let session = slot.open_ro_session()?;
358 let mut private = selector.clone();
359 private.push(Attribute::Class(ObjectClass::PRIVATE_KEY));
360 let mut public = selector;
361 public.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
362 let private_keys = session.find_objects(&private)?;
363 let public_keys = session.find_objects(&public)?;
364 if private_keys.len() != 1 || public_keys.len() != 1 {
365 return Err(HsmError::KeyNotFound(
366 "key selector must match exactly one key pair".into(),
367 ));
368 }
369 Ok(Self {
370 session,
371 private_key: private_keys[0],
372 public_key: public_keys[0],
373 algorithm,
374 })
375 }
376
377 pub fn from_uri(slot: &HsmSlot, uri: &str, algorithm: KeyAlgorithm) -> HsmResult<Self> {
389 let params = parse_uri(uri)?;
390 if let Some(token) = params.get("token")
391 && token != slot.token_label()?.as_bytes()
392 {
393 return Err(HsmError::UriParse(
394 "URI token does not match selected slot".into(),
395 ));
396 }
397 if params.get("type").is_some_and(|kind| kind != b"private") {
398 return Err(HsmError::UriParse("a private key URI is required".into()));
399 }
400 let mut selector = Vec::new();
401 if let Some(id) = params.get("id") {
402 selector.push(Attribute::Id(id.clone()));
403 }
404 if let Some(label) = params.get("object") {
405 selector.push(Attribute::Label(label.clone()));
406 }
407 if selector.is_empty() {
408 return Err(HsmError::UriParse("URI must contain id or object".into()));
409 }
410 Self::find_selected(slot, selector, algorithm)
411 }
412
413 pub fn private_key(&self) -> ObjectHandle {
415 self.private_key
416 }
417
418 pub fn public_key(&self) -> ObjectHandle {
420 self.public_key
421 }
422
423 pub fn session(&self) -> &Session {
425 &self.session
426 }
427
428 pub fn algorithm(&self) -> KeyAlgorithm {
430 self.algorithm
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn test_ecdsa_curve_oids() {
440 assert_eq!(EcdsaCurve::P256.oid().len(), 10);
441 assert_eq!(EcdsaCurve::P384.oid().len(), 7);
442 assert_eq!(EcdsaCurve::P521.oid().len(), 7);
443 }
444
445 #[test]
446 fn test_pkcs11_uri_parsing() {
447 let uri = "pkcs11:token=MyToken;object=MyKey;type=private";
448 let url = Url::parse(uri).unwrap();
449 assert_eq!(url.scheme(), "pkcs11");
450 }
451}
452
453pub fn parse_uri(uri: &str) -> HsmResult<HashMap<String, Vec<u8>>> {
455 let url = Url::parse(uri).map_err(|e| HsmError::UriParse(e.to_string()))?;
456
457 if url.scheme() != "pkcs11" {
458 return Err(HsmError::UriParse(format!(
459 "Invalid scheme '{}', expected 'pkcs11'",
460 url.scheme()
461 )));
462 }
463
464 if url.query().is_some() || url.fragment().is_some() || url.has_host() {
465 return Err(HsmError::UriParse(
466 "PKCS#11 URI query, fragment, and authority are unsupported".into(),
467 ));
468 }
469 let mut params: HashMap<String, Vec<u8>> = HashMap::new();
471 for part in url.path().split(';') {
472 let (name, value) = part
473 .split_once('=')
474 .ok_or_else(|| HsmError::UriParse("invalid PKCS#11 attribute".into()))?;
475 if !matches!(name, "token" | "object" | "id" | "type") {
476 return Err(HsmError::UriParse("unsupported PKCS#11 selector".into()));
477 }
478 let raw = value.as_bytes();
479 for (i, b) in raw.iter().enumerate() {
480 if *b == b'%'
481 && (i + 2 >= raw.len()
482 || !raw[i + 1].is_ascii_hexdigit()
483 || !raw[i + 2].is_ascii_hexdigit())
484 {
485 return Err(HsmError::UriParse("invalid percent escape".into()));
486 }
487 }
488 let bytes = percent_encoding::percent_decode_str(value).collect::<Vec<_>>();
489 if params.insert(name.to_string(), bytes).is_some() {
490 return Err(HsmError::UriParse("duplicate PKCS#11 attribute".into()));
491 }
492 }
493 Ok(params)
494}
495
496#[cfg(test)]
497mod uri_regressions {
498 use super::*;
499 #[test]
500 fn standard_binary_id_and_percent_encoded_label() {
501 let parsed =
502 parse_uri("pkcs11:token=Synthetic%20token;object=TLS%3Bkey;id=%00%ff;type=private")
503 .unwrap();
504 assert_eq!(parsed["token"], b"Synthetic token");
505 assert_eq!(parsed["object"], b"TLS;key");
506 assert_eq!(parsed["id"], [0, 255]);
507 }
508 #[test]
509 fn ambiguous_or_ignored_selectors_are_rejected() {
510 for uri in [
511 "pkcs11:object=a;object=b",
512 "pkcs11:object=a%2",
513 "pkcs11:object=a;unknown=x",
514 "pkcs11:object=a?pin-value=synthetic",
515 ] {
516 assert!(parse_uri(uri).is_err(), "{uri}");
517 }
518 }
519}