Skip to main content

aws_lc_rs/pqdsa/
key_pair.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4use 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/// A PQDSA (Post-Quantum Digital Signature Algorithm) key pair, used for signing and verification.
20#[allow(clippy::module_name_repetitions)]
21pub struct PqdsaKeyPair {
22    algorithm: &'static PqdsaSigningAlgorithm,
23    evp_pkey: LcPtr<EVP_PKEY>,
24    pubkey: PublicKey,
25}
26
27#[allow(clippy::missing_fields_in_debug)]
28impl Debug for PqdsaKeyPair {
29    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("PqdsaKeyPair")
31            .field("algorithm", &self.algorithm)
32            .finish()
33    }
34}
35
36impl KeyPair for PqdsaKeyPair {
37    type PublicKey = PublicKey;
38
39    fn public_key(&self) -> &Self::PublicKey {
40        &self.pubkey
41    }
42}
43
44/// A PQDSA private key.
45pub struct PqdsaPrivateKey<'a>(pub(crate) &'a PqdsaKeyPair);
46
47impl AsDer<Pkcs8V1Der<'static>> for PqdsaPrivateKey<'_> {
48    /// Serializes the key to PKCS#8 v1 DER.
49    ///
50    /// # Errors
51    /// Returns `Unspecified` if serialization fails.
52    fn as_der(&self) -> Result<Pkcs8V1Der<'static>, Unspecified> {
53        Ok(Pkcs8V1Der::new(
54            self.0
55                .evp_pkey
56                .as_const()
57                .marshal_rfc5208_private_key(pkcs8::Version::V1)?,
58        ))
59    }
60}
61
62impl AsRawBytes<PqdsaPrivateKeyRaw<'static>> for PqdsaPrivateKey<'_> {
63    fn as_raw_bytes(&self) -> Result<PqdsaPrivateKeyRaw<'static>, Unspecified> {
64        Ok(PqdsaPrivateKeyRaw::new(
65            self.0.evp_pkey.as_const().marshal_raw_private_key()?,
66        ))
67    }
68}
69
70impl PqdsaKeyPair {
71    /// Generates a new PQDSA key pair for the specified algorithm.
72    ///
73    /// # Errors
74    /// Returns `Unspecified` is the key generation fails.
75    pub fn generate(algorithm: &'static PqdsaSigningAlgorithm) -> Result<Self, Unspecified> {
76        let evp_pkey = evp_key_pqdsa_generate(algorithm.0.id.nid())?;
77        let pubkey = PublicKey::from_private_evp_pkey(&evp_pkey)?;
78        Ok(Self {
79            algorithm,
80            evp_pkey,
81            pubkey,
82        })
83    }
84
85    /// Constructs a key pair from the parsing of PKCS#8.
86    ///
87    /// # Errors
88    /// Returns `Unspecified` if the key is not valid for the specified signing algorithm.
89    pub fn from_pkcs8(
90        algorithm: &'static PqdsaSigningAlgorithm,
91        pkcs8: &[u8],
92    ) -> Result<Self, KeyRejected> {
93        let evp_pkey = LcPtr::<EVP_PKEY>::parse_rfc5208_private_key(pkcs8, EVP_PKEY_PQDSA)?;
94        validate_pqdsa_evp_key(&evp_pkey, algorithm.0.id)?;
95        let pubkey = PublicKey::from_private_evp_pkey(&evp_pkey)?;
96        Ok(Self {
97            algorithm,
98            evp_pkey,
99            pubkey,
100        })
101    }
102
103    /// Constructs a key pair from raw private key bytes.
104    ///
105    /// # Errors
106    /// Returns `Unspecified` if the key is not valid for the specified signing algorithm.
107    pub fn from_raw_private_key(
108        algorithm: &'static PqdsaSigningAlgorithm,
109        raw_private_key: &[u8],
110    ) -> Result<Self, KeyRejected> {
111        let evp_pkey = LcPtr::<EVP_PKEY>::parse_raw_private_key(raw_private_key, 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    /// Constructs a key pair deterministically from a 32-byte seed.
122    ///
123    /// Per FIPS 204, the same seed always produces the same key pair. This enables
124    /// reproducible key generation for testing, ACVP validation, and interoperability
125    /// with implementations that store seeds rather than expanded private keys.
126    ///
127    /// `algorithm` is the [`PqdsaSigningAlgorithm`] to be associated with the key pair.
128    ///
129    /// `seed` is the 32-byte seed from which the key pair is deterministically derived.
130    /// All ML-DSA variants (ML-DSA-44, ML-DSA-65, ML-DSA-87) use 32-byte seeds.
131    ///
132    /// # Security Considerations
133    ///
134    /// The seed is the root secret. Compromise of the seed is equivalent to compromise
135    /// of the private key. Callers are responsible for generating seeds from a
136    /// cryptographically secure random source and protecting them accordingly.
137    ///
138    /// This method expands the seed into the full private key internally. The seed
139    /// itself is not retained in the returned [`PqdsaKeyPair`]; the expanded key material
140    /// is stored instead. The expanded private key can be retrieved via
141    /// [`Self::private_key`] and serialized via [`Self::to_pkcs8`] or
142    /// [`PqdsaPrivateKey::as_raw_bytes`].
143    ///
144    /// # Errors
145    ///
146    /// Returns `KeyRejected::too_small()` if `seed.len() < 32`.
147    ///
148    /// Returns `KeyRejected::too_large()` if `seed.len() > 32`.
149    ///
150    /// Returns `KeyRejected::unspecified()` if the underlying cryptographic operation fails.
151    pub fn from_seed(
152        algorithm: &'static PqdsaSigningAlgorithm,
153        seed: &[u8],
154    ) -> Result<Self, KeyRejected> {
155        let expected_seed_len = algorithm.0.id.seed_size_bytes();
156        match seed.len().cmp(&expected_seed_len) {
157            core::cmp::Ordering::Less => return Err(KeyRejected::too_small()),
158            core::cmp::Ordering::Greater => return Err(KeyRejected::too_large()),
159            core::cmp::Ordering::Equal => {}
160        }
161        let nid = algorithm.0.id.nid();
162        let evp_pkey = LcPtr::new(unsafe {
163            EVP_PKEY_pqdsa_new_raw_private_key(nid, seed.as_ptr(), seed.len())
164        })
165        .map_err(|()| KeyRejected::unspecified())?;
166        validate_pqdsa_evp_key(&evp_pkey, algorithm.0.id)?;
167        let pubkey =
168            PublicKey::from_private_evp_pkey(&evp_pkey).map_err(|_| KeyRejected::unspecified())?;
169        Ok(Self {
170            algorithm,
171            evp_pkey,
172            pubkey,
173        })
174    }
175
176    /// Serializes the private key to PKCS#8 v1 DER.
177    ///
178    /// # Errors
179    /// Returns `Unspecified` if serialization fails.
180    pub fn to_pkcs8(&self) -> Result<Document, Unspecified> {
181        Ok(Document::new(
182            self.evp_pkey
183                .as_const()
184                .marshal_rfc5208_private_key(Version::V1)?,
185        ))
186    }
187
188    /// Uses this key to sign the message provided. The signature is written to the `signature`
189    /// slice provided. It returns the length of the signature on success.
190    ///
191    /// # Errors
192    /// Returns `Unspecified` if signing fails.
193    pub fn sign(&self, msg: &[u8], signature: &mut [u8]) -> Result<usize, Unspecified> {
194        let sig_length = self.algorithm.signature_len();
195        if signature.len() < sig_length {
196            return Err(Unspecified);
197        }
198        let sig_bytes = self.evp_pkey.sign(msg, None, No_EVP_PKEY_CTX_consumer)?;
199        signature[0..sig_length].copy_from_slice(&sig_bytes);
200        Ok(sig_length)
201    }
202
203    /// Returns the signing algorithm associated with this key pair.
204    #[must_use]
205    pub fn algorithm(&self) -> &'static PqdsaSigningAlgorithm {
206        self.algorithm
207    }
208
209    /// Returns the private key associated with this key pair.
210    #[must_use]
211    pub fn private_key(&self) -> PqdsaPrivateKey<'_> {
212        PqdsaPrivateKey(self)
213    }
214}
215
216unsafe impl Send for PqdsaKeyPair {}
217
218unsafe impl Sync for PqdsaKeyPair {}
219
220pub(crate) fn evp_key_pqdsa_generate(nid: c_int) -> Result<LcPtr<EVP_PKEY>, Unspecified> {
221    let params_fn = |ctx| {
222        if 1 == unsafe { EVP_PKEY_CTX_pqdsa_set_params(ctx, nid) } {
223            Ok(())
224        } else {
225            Err(())
226        }
227    };
228    LcPtr::<EVP_PKEY>::generate(EVP_PKEY_PQDSA, Some(params_fn))
229}
230
231#[cfg(all(test, feature = "unstable"))]
232mod tests {
233    use super::*;
234
235    use crate::signature::UnparsedPublicKey;
236    use crate::unstable::signature::{ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING};
237
238    const TEST_ALGORITHMS: &[&PqdsaSigningAlgorithm] =
239        &[&ML_DSA_44_SIGNING, &ML_DSA_65_SIGNING, &ML_DSA_87_SIGNING];
240
241    #[test]
242    fn test_public_key_serialization() {
243        for &alg in TEST_ALGORITHMS {
244            // Generate a new key pair
245            let keypair = PqdsaKeyPair::generate(alg).unwrap();
246            let message = b"Test message";
247            let different_message = b"Different message";
248            let mut signature = vec![0; alg.signature_len()];
249            assert!(keypair
250                .sign(message, &mut signature[0..(alg.signature_len() - 1)])
251                .is_err());
252            let sig_len = keypair.sign(message, &mut signature).unwrap();
253            assert_eq!(sig_len, alg.signature_len());
254            let invalid_signature = vec![0u8; alg.signature_len()];
255
256            let original_public_key = keypair.public_key();
257
258            let x509_der = original_public_key.as_der().unwrap();
259            let x509_public_key = UnparsedPublicKey::new(alg.0, x509_der.as_ref());
260            assert!(x509_public_key.verify(message, signature.as_ref()).is_ok());
261            assert!(x509_public_key
262                .verify(different_message, signature.as_ref())
263                .is_err());
264            assert!(x509_public_key.verify(message, &invalid_signature).is_err());
265
266            let raw = original_public_key.as_ref();
267            let raw_public_key = UnparsedPublicKey::new(alg.0, raw);
268            assert!(raw_public_key.verify(message, signature.as_ref()).is_ok());
269            assert!(raw_public_key
270                .verify(different_message, signature.as_ref())
271                .is_err());
272            assert!(raw_public_key
273                .verify(different_message, &invalid_signature)
274                .is_err());
275
276            #[cfg(feature = "ring-sig-verify")]
277            #[allow(deprecated)]
278            {
279                use crate::signature::VerificationAlgorithm;
280                assert!(alg
281                    .0
282                    .verify(
283                        raw.into(),
284                        message.as_ref().into(),
285                        signature.as_slice().into()
286                    )
287                    .is_ok());
288            }
289        }
290    }
291
292    #[test]
293    fn test_private_key_serialization() {
294        for &alg in TEST_ALGORITHMS {
295            // Generate a new key pair
296            let keypair = PqdsaKeyPair::generate(alg).unwrap();
297            let message = b"Test message";
298            let mut original_signature = vec![0; alg.signature_len()];
299            let sig_len = keypair.sign(message, &mut original_signature).unwrap();
300            assert_eq!(sig_len, alg.signature_len());
301
302            let public_key = keypair.public_key();
303            let unparsed_public_key = UnparsedPublicKey::new(alg.0, public_key.as_ref());
304            unparsed_public_key
305                .verify(message, original_signature.as_ref())
306                .unwrap();
307
308            let pkcs8_1 = keypair.to_pkcs8().unwrap();
309            let pkcs8_2 = keypair.private_key().as_der().unwrap();
310            let raw = keypair.private_key().as_raw_bytes().unwrap();
311
312            assert_eq!(pkcs8_1.as_ref(), pkcs8_2.as_ref());
313
314            let pkcs8_keypair = PqdsaKeyPair::from_pkcs8(alg, pkcs8_1.as_ref()).unwrap();
315            let raw_keypair = PqdsaKeyPair::from_raw_private_key(alg, raw.as_ref()).unwrap();
316
317            assert_eq!(pkcs8_keypair.evp_pkey, raw_keypair.evp_pkey);
318        }
319    }
320
321    #[test]
322    fn test_from_seed() {
323        for &alg in TEST_ALGORITHMS {
324            let seed = [1u8; 32];
325            let kp = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
326            assert_eq!(kp.algorithm(), alg);
327            // Verify key works for signing
328            let msg = b"seed test";
329            let mut sig = vec![0; alg.signature_len()];
330            let sig_len = kp.sign(msg, &mut sig).unwrap();
331            assert_eq!(sig_len, alg.signature_len());
332        }
333    }
334
335    #[test]
336    fn test_from_seed_deterministic() {
337        for &alg in TEST_ALGORITHMS {
338            let seed = [42u8; 32];
339            let kp1 = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
340            let kp2 = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
341            assert_eq!(kp1.public_key().as_ref(), kp2.public_key().as_ref());
342        }
343    }
344
345    #[test]
346    fn test_from_seed_wrong_size() {
347        use crate::error::KeyRejected;
348        for &alg in TEST_ALGORITHMS {
349            assert_eq!(
350                PqdsaKeyPair::from_seed(alg, &[0u8; 31]).err(),
351                Some(KeyRejected::too_small())
352            );
353            assert_eq!(
354                PqdsaKeyPair::from_seed(alg, &[0u8; 33]).err(),
355                Some(KeyRejected::too_large())
356            );
357            assert_eq!(
358                PqdsaKeyPair::from_seed(alg, &[]).err(),
359                Some(KeyRejected::too_small())
360            );
361        }
362    }
363
364    #[test]
365    fn test_from_seed_different_seeds_different_keys() {
366        for &alg in TEST_ALGORITHMS {
367            let kp1 = PqdsaKeyPair::from_seed(alg, &[1u8; 32]).unwrap();
368            let kp2 = PqdsaKeyPair::from_seed(alg, &[2u8; 32]).unwrap();
369            assert_ne!(kp1.public_key().as_ref(), kp2.public_key().as_ref());
370        }
371    }
372
373    #[test]
374    fn test_from_seed_raw_private_key_roundtrip() {
375        use crate::encoding::AsRawBytes;
376        for &alg in TEST_ALGORITHMS {
377            let seed = [55u8; 32];
378            let kp = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
379            let raw_bytes = kp.private_key().as_raw_bytes().unwrap();
380            let kp2 = PqdsaKeyPair::from_raw_private_key(alg, raw_bytes.as_ref()).unwrap();
381            assert_eq!(kp.public_key().as_ref(), kp2.public_key().as_ref());
382        }
383    }
384
385    #[test]
386    fn test_from_seed_pkcs8_roundtrip() {
387        for &alg in TEST_ALGORITHMS {
388            let seed = [77u8; 32];
389            let kp = PqdsaKeyPair::from_seed(alg, &seed).unwrap();
390            let pkcs8 = kp.to_pkcs8().unwrap();
391            let kp2 = PqdsaKeyPair::from_pkcs8(alg, pkcs8.as_ref()).unwrap();
392            assert_eq!(kp.public_key().as_ref(), kp2.public_key().as_ref());
393        }
394    }
395
396    #[test]
397    fn test_from_seed_same_seed_different_algorithms() {
398        // Same seed with different algorithms should produce different keys
399        let seed = [42u8; 32];
400        let kp_44 = PqdsaKeyPair::from_seed(&ML_DSA_44_SIGNING, &seed).unwrap();
401        let kp_65 = PqdsaKeyPair::from_seed(&ML_DSA_65_SIGNING, &seed).unwrap();
402        let kp_87 = PqdsaKeyPair::from_seed(&ML_DSA_87_SIGNING, &seed).unwrap();
403        // Public keys have different sizes across algorithms, so they must differ
404        assert_ne!(
405            kp_44.public_key().as_ref().len(),
406            kp_65.public_key().as_ref().len()
407        );
408        assert_ne!(
409            kp_65.public_key().as_ref().len(),
410            kp_87.public_key().as_ref().len()
411        );
412    }
413
414    // Additional test for the algorithm getter
415    #[test]
416    fn test_algorithm_getter() {
417        for &alg in TEST_ALGORITHMS {
418            let keypair = PqdsaKeyPair::generate(alg).unwrap();
419            assert_eq!(keypair.algorithm(), alg);
420        }
421    }
422
423    // Additional test for the algorithm getter
424    #[test]
425    fn test_debug() {
426        for &alg in TEST_ALGORITHMS {
427            let keypair = PqdsaKeyPair::generate(alg).unwrap();
428            assert!(
429                format!("{keypair:?}").starts_with("PqdsaKeyPair { algorithm: PqdsaSigningAlgorithm(PqdsaVerificationAlgorithm { id:"),
430                "{keypair:?}"
431            );
432            let pubkey = keypair.public_key();
433            assert!(
434                format!("{pubkey:?}").starts_with("PqdsaPublicKey("),
435                "{pubkey:?}"
436            );
437        }
438    }
439}