1use crate::aws_lc::{
5 EVP_PKEY_CTX_pqdsa_set_params, EVP_PKEY_pqdsa_new_raw_private_key, EVP_PKEY, EVP_PKEY_PQDSA,
6};
7use crate::encoding::{AsDer, AsRawBytes, Pkcs8V1Der, PqdsaPrivateKeyRaw};
8use crate::error::{KeyRejected, Unspecified};
9use crate::evp_pkey::No_EVP_PKEY_CTX_consumer;
10use crate::pkcs8;
11use crate::pkcs8::{Document, Version};
12use crate::pqdsa::signature::{PqdsaSigningAlgorithm, PublicKey};
13use crate::pqdsa::validate_pqdsa_evp_key;
14use crate::ptr::LcPtr;
15use crate::signature::KeyPair;
16use core::fmt::{Debug, Formatter};
17use std::ffi::c_int;
18
19#[derive(Clone)]
21#[allow(clippy::module_name_repetitions)]
22pub struct PqdsaKeyPair {
23 algorithm: &'static PqdsaSigningAlgorithm,
24 evp_pkey: LcPtr<EVP_PKEY>,
25 pubkey: PublicKey,
26}
27
28#[allow(clippy::missing_fields_in_debug)]
29impl Debug for PqdsaKeyPair {
30 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31 f.debug_struct("PqdsaKeyPair")
32 .field("algorithm", &self.algorithm)
33 .finish()
34 }
35}
36
37impl KeyPair for PqdsaKeyPair {
38 type PublicKey = PublicKey;
39
40 fn public_key(&self) -> &Self::PublicKey {
41 &self.pubkey
42 }
43}
44
45pub struct PqdsaPrivateKey<'a>(pub(crate) &'a PqdsaKeyPair);
47
48impl Debug for PqdsaPrivateKey<'_> {
49 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50 f.write_str(&format!("PqdsaPrivateKey({:?})", self.0.algorithm.0.id))
51 }
52}
53
54impl AsDer<Pkcs8V1Der<'static>> for PqdsaPrivateKey<'_> {
55 fn as_der(&self) -> Result<Pkcs8V1Der<'static>, Unspecified> {
62 Ok(Pkcs8V1Der::new(
63 self.0
64 .evp_pkey
65 .as_const()
66 .marshal_rfc5208_private_key(pkcs8::Version::V1)?,
67 ))
68 }
69}
70
71impl AsRawBytes<PqdsaPrivateKeyRaw<'static>> for PqdsaPrivateKey<'_> {
72 fn as_raw_bytes(&self) -> Result<PqdsaPrivateKeyRaw<'static>, Unspecified> {
76 Ok(PqdsaPrivateKeyRaw::new(
77 self.0.evp_pkey.as_const().marshal_raw_private_key()?,
78 ))
79 }
80}
81
82impl PqdsaKeyPair {
83 pub fn generate(algorithm: &'static PqdsaSigningAlgorithm) -> Result<Self, Unspecified> {
91 let evp_pkey = evp_key_pqdsa_generate(algorithm.0.id.nid())?;
92 let pubkey = PublicKey::from_private_evp_pkey(&evp_pkey)?;
93 Ok(Self {
94 algorithm,
95 evp_pkey,
96 pubkey,
97 })
98 }
99
100 pub fn from_pkcs8(
108 algorithm: &'static PqdsaSigningAlgorithm,
109 pkcs8: &[u8],
110 ) -> Result<Self, KeyRejected> {
111 let evp_pkey = LcPtr::<EVP_PKEY>::parse_rfc5208_private_key(pkcs8, EVP_PKEY_PQDSA)?;
112 validate_pqdsa_evp_key(&evp_pkey, algorithm.0.id)?;
113 let pubkey = PublicKey::from_private_evp_pkey(&evp_pkey)?;
114 Ok(Self {
115 algorithm,
116 evp_pkey,
117 pubkey,
118 })
119 }
120
121 pub fn from_raw_private_key(
128 algorithm: &'static PqdsaSigningAlgorithm,
129 raw_private_key: &[u8],
130 ) -> Result<Self, KeyRejected> {
131 let evp_pkey = LcPtr::<EVP_PKEY>::parse_raw_private_key(raw_private_key, EVP_PKEY_PQDSA)?;
132 validate_pqdsa_evp_key(&evp_pkey, algorithm.0.id)?;
133 let pubkey = PublicKey::from_private_evp_pkey(&evp_pkey)?;
134 Ok(Self {
135 algorithm,
136 evp_pkey,
137 pubkey,
138 })
139 }
140
141 pub fn from_seed(
177 algorithm: &'static PqdsaSigningAlgorithm,
178 seed: &[u8],
179 ) -> Result<Self, KeyRejected> {
180 let expected_seed_len = algorithm.0.id.seed_size_bytes();
181 match seed.len().cmp(&expected_seed_len) {
182 core::cmp::Ordering::Less => return Err(KeyRejected::too_small()),
183 core::cmp::Ordering::Greater => return Err(KeyRejected::too_large()),
184 core::cmp::Ordering::Equal => {}
185 }
186 let nid = algorithm.0.id.nid();
187 let evp_pkey = LcPtr::new(unsafe {
188 EVP_PKEY_pqdsa_new_raw_private_key(nid, seed.as_ptr(), seed.len())
189 })
190 .map_err(|()| KeyRejected::unspecified())?;
191 validate_pqdsa_evp_key(&evp_pkey, algorithm.0.id)?;
192 let pubkey =
193 PublicKey::from_private_evp_pkey(&evp_pkey).map_err(|_| KeyRejected::unspecified())?;
194 Ok(Self {
195 algorithm,
196 evp_pkey,
197 pubkey,
198 })
199 }
200
201 pub fn to_pkcs8v1(&self) -> Result<Document, Unspecified> {
211 Ok(Document::new(
212 self.evp_pkey
213 .as_const()
214 .marshal_rfc5208_private_key(Version::V1)?,
215 ))
216 }
217
218 #[cfg(feature = "unstable")]
226 #[deprecated(since = "1.18.0", note = "use `PqdsaKeyPair::to_pkcs8v1`")]
227 pub fn to_pkcs8(&self) -> Result<Document, Unspecified> {
228 self.to_pkcs8v1()
229 }
230
231 pub fn sign(&self, msg: &[u8], signature: &mut [u8]) -> Result<usize, Unspecified> {
241 let sig_length = self.algorithm.signature_len();
242 if signature.len() < sig_length {
243 return Err(Unspecified);
244 }
245 let sig_bytes = self.evp_pkey.sign(msg, None, No_EVP_PKEY_CTX_consumer)?;
246 signature[0..sig_length].copy_from_slice(&sig_bytes);
247 Ok(sig_length)
248 }
249
250 #[must_use]
252 pub fn algorithm(&self) -> &'static PqdsaSigningAlgorithm {
253 self.algorithm
254 }
255
256 #[must_use]
258 pub fn private_key(&self) -> PqdsaPrivateKey<'_> {
259 PqdsaPrivateKey(self)
260 }
261}
262
263unsafe impl Send for PqdsaKeyPair {}
264
265unsafe impl Sync for PqdsaKeyPair {}
266
267pub(crate) fn evp_key_pqdsa_generate(nid: c_int) -> Result<LcPtr<EVP_PKEY>, Unspecified> {
268 let params_fn = |ctx| {
269 if 1 == unsafe { EVP_PKEY_CTX_pqdsa_set_params(ctx, nid) } {
270 Ok(())
271 } else {
272 Err(())
273 }
274 };
275 LcPtr::<EVP_PKEY>::generate(EVP_PKEY_PQDSA, Some(params_fn))
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 use crate::signature::{
283 UnparsedPublicKey, ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING,
284 };
285
286 const TEST_ALGORITHMS: &[&PqdsaSigningAlgorithm] =
287 &[&ML_DSA_44_SIGNING, &ML_DSA_65_SIGNING, &ML_DSA_87_SIGNING];
288
289 #[test]
290 fn test_public_key_serialization() {
291 for &alg in TEST_ALGORITHMS {
292 let keypair = PqdsaKeyPair::generate(alg).unwrap();
294 let message = b"Test message";
295 let different_message = b"Different message";
296 let mut signature = vec![0; alg.signature_len()];
297 assert!(keypair
298 .sign(message, &mut signature[0..(alg.signature_len() - 1)])
299 .is_err());
300 let sig_len = keypair.sign(message, &mut signature).unwrap();
301 assert_eq!(sig_len, alg.signature_len());
302 let invalid_signature = vec![0u8; alg.signature_len()];
303
304 let original_public_key = keypair.public_key();
305
306 let x509_der = original_public_key.as_der().unwrap();
307 let x509_public_key = UnparsedPublicKey::new(alg.0, x509_der.as_ref());
308 assert!(x509_public_key.verify(message, signature.as_ref()).is_ok());
309 assert!(x509_public_key
310 .verify(different_message, signature.as_ref())
311 .is_err());
312 assert!(x509_public_key.verify(message, &invalid_signature).is_err());
313
314 let raw = original_public_key.as_ref();
315 let raw_public_key = UnparsedPublicKey::new(alg.0, raw);
316 assert!(raw_public_key.verify(message, signature.as_ref()).is_ok());
317 assert!(raw_public_key
318 .verify(different_message, signature.as_ref())
319 .is_err());
320 assert!(raw_public_key
321 .verify(different_message, &invalid_signature)
322 .is_err());
323
324 #[cfg(feature = "ring-sig-verify")]
325 #[allow(deprecated)]
326 {
327 use crate::signature::VerificationAlgorithm;
328 assert!(alg
329 .0
330 .verify(
331 raw.into(),
332 message.as_ref().into(),
333 signature.as_slice().into()
334 )
335 .is_ok());
336 }
337 }
338 }
339
340 #[test]
341 fn test_private_key_serialization() {
342 for &alg in TEST_ALGORITHMS {
343 let keypair = PqdsaKeyPair::generate(alg).unwrap();
345 let message = b"Test message";
346 let mut original_signature = vec![0; alg.signature_len()];
347 let sig_len = keypair.sign(message, &mut original_signature).unwrap();
348 assert_eq!(sig_len, alg.signature_len());
349
350 let public_key = keypair.public_key();
351 let unparsed_public_key = UnparsedPublicKey::new(alg.0, public_key.as_ref());
352 unparsed_public_key
353 .verify(message, original_signature.as_ref())
354 .unwrap();
355
356 let pkcs8_1 = keypair.to_pkcs8v1().unwrap();
357 let pkcs8_2 = keypair.private_key().as_der().unwrap();
358 let raw = keypair.private_key().as_raw_bytes().unwrap();
359
360 assert_eq!(pkcs8_1.as_ref(), pkcs8_2.as_ref());
361
362 let pkcs8_keypair = PqdsaKeyPair::from_pkcs8(alg, pkcs8_1.as_ref()).unwrap();
363 let raw_keypair = PqdsaKeyPair::from_raw_private_key(alg, raw.as_ref()).unwrap();
364
365 assert_eq!(pkcs8_keypair.evp_pkey, raw_keypair.evp_pkey);
366 }
367 }
368
369 #[test]
370 fn test_from_seed() {
371 for &alg in TEST_ALGORITHMS {
372 let seed = [1u8; 32];
373 let kp = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
374 assert_eq!(kp.algorithm(), alg);
375 let msg = b"seed test";
377 let mut sig = vec![0u8; alg.signature_len()];
378 let sig_len = kp.sign(msg, &mut sig).unwrap();
379 assert_eq!(sig_len, alg.signature_len());
380 let public_key = UnparsedPublicKey::new(alg.0, kp.public_key().as_ref());
381 public_key.verify(msg, &sig).unwrap();
382 }
383 }
384
385 #[test]
386 fn test_from_seed_deterministic() {
387 for &alg in TEST_ALGORITHMS {
388 let seed = [42u8; 32];
389 let kp1 = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
390 let kp2 = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
391 assert_eq!(kp1.public_key().as_ref(), kp2.public_key().as_ref());
392 }
393 }
394
395 #[test]
396 fn test_from_seed_wrong_size() {
397 use crate::error::KeyRejected;
398 for &alg in TEST_ALGORITHMS {
399 assert_eq!(
400 PqdsaKeyPair::from_seed(alg, &[0u8; 31]).err(),
401 Some(KeyRejected::too_small())
402 );
403 assert_eq!(
404 PqdsaKeyPair::from_seed(alg, &[0u8; 33]).err(),
405 Some(KeyRejected::too_large())
406 );
407 assert_eq!(
408 PqdsaKeyPair::from_seed(alg, &[]).err(),
409 Some(KeyRejected::too_small())
410 );
411 }
412 }
413
414 #[test]
415 fn test_from_seed_different_seeds_different_keys() {
416 for &alg in TEST_ALGORITHMS {
417 let kp1 = PqdsaKeyPair::from_seed(alg, &[1u8; 32]).unwrap();
418 let kp2 = PqdsaKeyPair::from_seed(alg, &[2u8; 32]).unwrap();
419 assert_ne!(kp1.public_key().as_ref(), kp2.public_key().as_ref());
420 }
421 }
422
423 #[test]
424 fn test_from_seed_raw_private_key_roundtrip() {
425 use crate::encoding::AsRawBytes;
426 for &alg in TEST_ALGORITHMS {
427 let seed = [55u8; 32];
428 let kp = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
429 let raw_bytes = kp.private_key().as_raw_bytes().unwrap();
430 let kp2 = PqdsaKeyPair::from_raw_private_key(alg, raw_bytes.as_ref()).unwrap();
431 assert_eq!(kp.public_key().as_ref(), kp2.public_key().as_ref());
432 }
433 }
434
435 #[test]
436 fn test_from_seed_pkcs8_roundtrip() {
437 for &alg in TEST_ALGORITHMS {
438 let seed = [77u8; 32];
439 let kp = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
440 let pkcs8 = kp.to_pkcs8v1().unwrap();
441 let kp2 = PqdsaKeyPair::from_pkcs8(alg, pkcs8.as_ref()).unwrap();
442 assert_eq!(kp.public_key().as_ref(), kp2.public_key().as_ref());
443 }
444 }
445
446 #[test]
447 fn test_from_seed_same_seed_different_algorithms() {
448 let seed = [42u8; 32];
450 let kp_44 = PqdsaKeyPair::from_seed(&ML_DSA_44_SIGNING, &seed).unwrap();
451 let kp_65 = PqdsaKeyPair::from_seed(&ML_DSA_65_SIGNING, &seed).unwrap();
452 let kp_87 = PqdsaKeyPair::from_seed(&ML_DSA_87_SIGNING, &seed).unwrap();
453 assert_ne!(
455 kp_44.public_key().as_ref().len(),
456 kp_65.public_key().as_ref().len()
457 );
458 assert_ne!(
459 kp_65.public_key().as_ref().len(),
460 kp_87.public_key().as_ref().len()
461 );
462 }
463
464 #[test]
468 fn test_from_raw_private_key_rejects_seed() {
469 for &alg in TEST_ALGORITHMS {
470 assert!(PqdsaKeyPair::from_raw_private_key(alg, &[7u8; 32]).is_err());
473 }
474 }
475
476 #[test]
480 fn test_from_seed_pkcs8_is_seed_form() {
481 for &alg in TEST_ALGORITHMS {
482 let kp = PqdsaKeyPair::from_seed(alg, &[3u8; 32]).unwrap();
483 let pkcs8 = kp.to_pkcs8v1().unwrap();
484 let raw_expanded = kp.private_key().as_raw_bytes().unwrap();
485 assert!(
488 pkcs8.as_ref().len() < raw_expanded.as_ref().len(),
489 "seed-form pkcs8 ({}) should be smaller than expanded key ({})",
490 pkcs8.as_ref().len(),
491 raw_expanded.as_ref().len(),
492 );
493 assert!(
494 pkcs8.as_ref().len() < 128,
495 "seed-form pkcs8 should be compact, got {}",
496 pkcs8.as_ref().len(),
497 );
498 }
499 }
500
501 #[test]
507 fn test_expanded_key_pkcs8_unavailable() {
508 for &alg in TEST_ALGORITHMS {
509 let seed_kp = PqdsaKeyPair::from_seed(alg, &[4u8; 32]).unwrap();
511 let raw = seed_kp.private_key().as_raw_bytes().unwrap();
512 let expanded_kp = PqdsaKeyPair::from_raw_private_key(alg, raw.as_ref()).unwrap();
513
514 assert!(expanded_kp.private_key().as_raw_bytes().is_ok());
517 assert!(
519 expanded_kp.to_pkcs8v1().is_err(),
520 "expanded-only key unexpectedly serialized to PKCS#8",
521 );
522 }
523 }
524
525 #[test]
529 fn test_from_seed_pkcs8_roundtrip_preserves_seed_form() {
530 for &alg in TEST_ALGORITHMS {
531 let kp = PqdsaKeyPair::from_seed(alg, &[8u8; 32]).unwrap();
532 let pkcs8 = kp.to_pkcs8v1().unwrap();
533 let reparsed = PqdsaKeyPair::from_pkcs8(alg, pkcs8.as_ref()).unwrap();
534 let pkcs8_again = reparsed.to_pkcs8v1().unwrap();
535 assert_eq!(pkcs8.as_ref(), pkcs8_again.as_ref());
537 assert_eq!(kp.public_key().as_ref(), reparsed.public_key().as_ref());
539 }
540 }
541
542 #[test]
546 fn test_from_seed_as_der_matches_to_pkcs8v1() {
547 for &alg in TEST_ALGORITHMS {
548 let kp = PqdsaKeyPair::from_seed(alg, &[11u8; 32]).unwrap();
549 let pkcs8 = kp.to_pkcs8v1().unwrap();
550 let der = kp.private_key().as_der().unwrap();
551 assert_eq!(pkcs8.as_ref(), der.as_ref());
552 }
553 }
554
555 #[test]
557 fn test_algorithm_getter() {
558 for &alg in TEST_ALGORITHMS {
559 let keypair = PqdsaKeyPair::generate(alg).unwrap();
560 assert_eq!(keypair.algorithm(), alg);
561 }
562 }
563
564 #[test]
565 fn test_algorithm_lengths() {
566 for (alg, signature_len, public_key_len) in [
567 (&ML_DSA_44_SIGNING, 2420, 1312),
568 (&ML_DSA_65_SIGNING, 3309, 1952),
569 (&ML_DSA_87_SIGNING, 4627, 2592),
570 ] {
571 assert_eq!(alg.signature_len(), signature_len);
572 assert_eq!(alg.public_key_len(), public_key_len);
573 assert_eq!(alg.seed_len(), 32);
574 }
575 }
576
577 #[cfg(feature = "unstable")]
580 #[allow(deprecated)]
581 #[test]
582 fn test_to_pkcs8_deprecated_alias() {
583 for &alg in TEST_ALGORITHMS {
584 let kp = PqdsaKeyPair::from_seed(alg, &[9u8; 32]).unwrap();
585 assert_eq!(
586 kp.to_pkcs8().unwrap().as_ref(),
587 kp.to_pkcs8v1().unwrap().as_ref()
588 );
589 }
590 }
591
592 #[test]
593 fn test_debug() {
594 for &alg in TEST_ALGORITHMS {
595 let keypair = PqdsaKeyPair::generate(alg).unwrap();
596 assert!(
597 format!("{keypair:?}").starts_with("PqdsaKeyPair { algorithm: PqdsaSigningAlgorithm(PqdsaVerificationAlgorithm { id:"),
598 "{keypair:?}"
599 );
600 let pubkey = keypair.public_key();
601 assert!(
602 format!("{pubkey:?}").starts_with("PqdsaPublicKey("),
603 "{pubkey:?}"
604 );
605 let privkey = keypair.private_key();
606 assert!(
607 format!("{privkey:?}").starts_with("PqdsaPrivateKey("),
608 "{privkey:?}"
609 );
610 }
611 }
612}