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::{EVP_PKEY_CTX_pqdsa_set_params, EVP_PKEY, EVP_PKEY_PQDSA};
5use crate::encoding::{AsDer, AsRawBytes, Pkcs8V1Der, PqdsaPrivateKeyRaw};
6use crate::error::{KeyRejected, Unspecified};
7use crate::evp_pkey::No_EVP_PKEY_CTX_consumer;
8use crate::pkcs8;
9use crate::pkcs8::{Document, Version};
10use crate::pqdsa::signature::{PqdsaSigningAlgorithm, PublicKey};
11use crate::pqdsa::validate_pqdsa_evp_key;
12use crate::ptr::LcPtr;
13use crate::signature::KeyPair;
14use core::fmt::{Debug, Formatter};
15use std::ffi::c_int;
16
17/// A PQDSA (Post-Quantum Digital Signature Algorithm) key pair, used for signing and verification.
18#[allow(clippy::module_name_repetitions)]
19pub struct PqdsaKeyPair {
20    algorithm: &'static PqdsaSigningAlgorithm,
21    evp_pkey: LcPtr<EVP_PKEY>,
22    pubkey: PublicKey,
23}
24
25#[allow(clippy::missing_fields_in_debug)]
26impl Debug for PqdsaKeyPair {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        f.debug_struct("PqdsaKeyPair")
29            .field("algorithm", &self.algorithm)
30            .finish()
31    }
32}
33
34impl KeyPair for PqdsaKeyPair {
35    type PublicKey = PublicKey;
36
37    fn public_key(&self) -> &Self::PublicKey {
38        &self.pubkey
39    }
40}
41
42/// A PQDSA private key.
43pub struct PqdsaPrivateKey<'a>(pub(crate) &'a PqdsaKeyPair);
44
45impl AsDer<Pkcs8V1Der<'static>> for PqdsaPrivateKey<'_> {
46    /// Serializes the key to PKCS#8 v1 DER.
47    ///
48    /// # Errors
49    /// Returns `Unspecified` if serialization fails.
50    fn as_der(&self) -> Result<Pkcs8V1Der<'static>, Unspecified> {
51        Ok(Pkcs8V1Der::new(
52            self.0
53                .evp_pkey
54                .as_const()
55                .marshal_rfc5208_private_key(pkcs8::Version::V1)?,
56        ))
57    }
58}
59
60impl AsRawBytes<PqdsaPrivateKeyRaw<'static>> for PqdsaPrivateKey<'_> {
61    fn as_raw_bytes(&self) -> Result<PqdsaPrivateKeyRaw<'static>, Unspecified> {
62        Ok(PqdsaPrivateKeyRaw::new(
63            self.0.evp_pkey.as_const().marshal_raw_private_key()?,
64        ))
65    }
66}
67
68impl PqdsaKeyPair {
69    /// Generates a new PQDSA key pair for the specified algorithm.
70    ///
71    /// # Errors
72    /// Returns `Unspecified` is the key generation fails.
73    pub fn generate(algorithm: &'static PqdsaSigningAlgorithm) -> Result<Self, Unspecified> {
74        let evp_pkey = evp_key_pqdsa_generate(algorithm.0.id.nid())?;
75        let pubkey = PublicKey::from_private_evp_pkey(&evp_pkey)?;
76        Ok(Self {
77            algorithm,
78            evp_pkey,
79            pubkey,
80        })
81    }
82
83    /// Constructs a key pair from the parsing of PKCS#8.
84    ///
85    /// # Errors
86    /// Returns `Unspecified` if the key is not valid for the specified signing algorithm.
87    pub fn from_pkcs8(
88        algorithm: &'static PqdsaSigningAlgorithm,
89        pkcs8: &[u8],
90    ) -> Result<Self, KeyRejected> {
91        let evp_pkey = LcPtr::<EVP_PKEY>::parse_rfc5208_private_key(pkcs8, EVP_PKEY_PQDSA)?;
92        validate_pqdsa_evp_key(&evp_pkey, algorithm.0.id)?;
93        let pubkey = PublicKey::from_private_evp_pkey(&evp_pkey)?;
94        Ok(Self {
95            algorithm,
96            evp_pkey,
97            pubkey,
98        })
99    }
100
101    /// Constructs a key pair from raw private key bytes.
102    ///
103    /// # Errors
104    /// Returns `Unspecified` if the key is not valid for the specified signing algorithm.
105    pub fn from_raw_private_key(
106        algorithm: &'static PqdsaSigningAlgorithm,
107        raw_private_key: &[u8],
108    ) -> Result<Self, KeyRejected> {
109        let evp_pkey = LcPtr::<EVP_PKEY>::parse_raw_private_key(raw_private_key, EVP_PKEY_PQDSA)?;
110        validate_pqdsa_evp_key(&evp_pkey, algorithm.0.id)?;
111        let pubkey = PublicKey::from_private_evp_pkey(&evp_pkey)?;
112        Ok(Self {
113            algorithm,
114            evp_pkey,
115            pubkey,
116        })
117    }
118
119    /// Serializes the private key to PKCS#8 v1 DER.
120    ///
121    /// # Errors
122    /// Returns `Unspecified` if serialization fails.
123    pub fn to_pkcs8(&self) -> Result<Document, Unspecified> {
124        Ok(Document::new(
125            self.evp_pkey
126                .as_const()
127                .marshal_rfc5208_private_key(Version::V1)?,
128        ))
129    }
130
131    /// Uses this key to sign the message provided. The signature is written to the `signature`
132    /// slice provided. It returns the length of the signature on success.
133    ///
134    /// # Errors
135    /// Returns `Unspecified` if signing fails.
136    pub fn sign(&self, msg: &[u8], signature: &mut [u8]) -> Result<usize, Unspecified> {
137        let sig_length = self.algorithm.signature_len();
138        if signature.len() < sig_length {
139            return Err(Unspecified);
140        }
141        let sig_bytes = self.evp_pkey.sign(msg, None, No_EVP_PKEY_CTX_consumer)?;
142        signature[0..sig_length].copy_from_slice(&sig_bytes);
143        Ok(sig_length)
144    }
145
146    /// Returns the signing algorithm associated with this key pair.
147    #[must_use]
148    pub fn algorithm(&self) -> &'static PqdsaSigningAlgorithm {
149        self.algorithm
150    }
151
152    /// Returns the private key associated with this key pair.
153    #[must_use]
154    pub fn private_key(&self) -> PqdsaPrivateKey<'_> {
155        PqdsaPrivateKey(self)
156    }
157}
158
159unsafe impl Send for PqdsaKeyPair {}
160
161unsafe impl Sync for PqdsaKeyPair {}
162
163pub(crate) fn evp_key_pqdsa_generate(nid: c_int) -> Result<LcPtr<EVP_PKEY>, Unspecified> {
164    let params_fn = |ctx| {
165        if 1 == unsafe { EVP_PKEY_CTX_pqdsa_set_params(ctx, nid) } {
166            Ok(())
167        } else {
168            Err(())
169        }
170    };
171    LcPtr::<EVP_PKEY>::generate(EVP_PKEY_PQDSA, Some(params_fn))
172}
173
174#[cfg(all(test, feature = "unstable"))]
175mod tests {
176    use super::*;
177
178    use crate::signature::{UnparsedPublicKey, VerificationAlgorithm};
179    use crate::unstable::signature::{ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING};
180
181    const TEST_ALGORITHMS: &[&PqdsaSigningAlgorithm] =
182        &[&ML_DSA_44_SIGNING, &ML_DSA_65_SIGNING, &ML_DSA_87_SIGNING];
183
184    #[test]
185    fn test_public_key_serialization() {
186        for &alg in TEST_ALGORITHMS {
187            // Generate a new key pair
188            let keypair = PqdsaKeyPair::generate(alg).unwrap();
189            let message = b"Test message";
190            let different_message = b"Different message";
191            let mut signature = vec![0; alg.signature_len()];
192            assert!(keypair
193                .sign(message, &mut signature[0..(alg.signature_len() - 1)])
194                .is_err());
195            let sig_len = keypair.sign(message, &mut signature).unwrap();
196            assert_eq!(sig_len, alg.signature_len());
197            let invalid_signature = vec![0u8; alg.signature_len()];
198
199            let original_public_key = keypair.public_key();
200
201            let x509_der = original_public_key.as_der().unwrap();
202            let x509_public_key = UnparsedPublicKey::new(alg.0, x509_der.as_ref());
203            assert!(x509_public_key.verify(message, signature.as_ref()).is_ok());
204            assert!(x509_public_key
205                .verify(different_message, signature.as_ref())
206                .is_err());
207            assert!(x509_public_key.verify(message, &invalid_signature).is_err());
208
209            let raw = original_public_key.as_ref();
210            let raw_public_key = UnparsedPublicKey::new(alg.0, raw);
211            assert!(raw_public_key.verify(message, signature.as_ref()).is_ok());
212            assert!(raw_public_key
213                .verify(different_message, signature.as_ref())
214                .is_err());
215            assert!(raw_public_key
216                .verify(different_message, &invalid_signature)
217                .is_err());
218
219            #[cfg(feature = "ring-sig-verify")]
220            #[allow(deprecated)]
221            {
222                assert!(alg
223                    .0
224                    .verify(
225                        raw.into(),
226                        message.as_ref().into(),
227                        signature.as_slice().into()
228                    )
229                    .is_ok());
230            }
231        }
232    }
233
234    #[test]
235    fn test_private_key_serialization() {
236        for &alg in TEST_ALGORITHMS {
237            // Generate a new key pair
238            let keypair = PqdsaKeyPair::generate(alg).unwrap();
239            let message = b"Test message";
240            let mut original_signature = vec![0; alg.signature_len()];
241            let sig_len = keypair.sign(message, &mut original_signature).unwrap();
242            assert_eq!(sig_len, alg.signature_len());
243
244            let public_key = keypair.public_key();
245            let unparsed_public_key = UnparsedPublicKey::new(alg.0, public_key.as_ref());
246            unparsed_public_key
247                .verify(message, original_signature.as_ref())
248                .unwrap();
249
250            let pkcs8_1 = keypair.to_pkcs8().unwrap();
251            let pkcs8_2 = keypair.private_key().as_der().unwrap();
252            let raw = keypair.private_key().as_raw_bytes().unwrap();
253
254            assert_eq!(pkcs8_1.as_ref(), pkcs8_2.as_ref());
255
256            let pkcs8_keypair = PqdsaKeyPair::from_pkcs8(alg, pkcs8_1.as_ref()).unwrap();
257            let raw_keypair = PqdsaKeyPair::from_raw_private_key(alg, raw.as_ref()).unwrap();
258
259            assert_eq!(pkcs8_keypair.evp_pkey, raw_keypair.evp_pkey);
260        }
261    }
262
263    // Additional test for the algorithm getter
264    #[test]
265    fn test_algorithm_getter() {
266        for &alg in TEST_ALGORITHMS {
267            let keypair = PqdsaKeyPair::generate(alg).unwrap();
268            assert_eq!(keypair.algorithm(), alg);
269        }
270    }
271
272    // Additional test for the algorithm getter
273    #[test]
274    fn test_debug() {
275        for &alg in TEST_ALGORITHMS {
276            let keypair = PqdsaKeyPair::generate(alg).unwrap();
277            assert!(
278                format!("{keypair:?}").starts_with("PqdsaKeyPair { algorithm: PqdsaSigningAlgorithm(PqdsaVerificationAlgorithm { id:"),
279                "{keypair:?}"
280            );
281            let pubkey = keypair.public_key();
282            assert!(
283                format!("{pubkey:?}").starts_with("PqdsaPublicKey("),
284                "{pubkey:?}"
285            );
286        }
287    }
288}