quantcrypt/asn1/
private_key.rs

1use der::{Decode, Encode};
2use pem::EncodeConfig;
3use pkcs8::spki::{self, AlgorithmIdentifierOwned, DynSignatureAlgorithmIdentifier};
4use pkcs8::ObjectIdentifier;
5use pkcs8::{spki::AlgorithmIdentifier, PrivateKeyInfo};
6
7use crate::asn1::asn_util::{is_composite_kem_or_dsa_oid, is_valid_kem_or_dsa_oid};
8use crate::asn1::signature::DsaSignature;
9use crate::dsa::common::dsa_trait::Dsa;
10use crate::dsa::dsa_manager::DsaManager;
11use crate::kem::common::kem_trait::Kem;
12use crate::kem::kem_manager::KemManager;
13use crate::{asn1::composite_private_key::CompositePrivateKey, errors};
14use crate::{keys::PublicKey, QuantCryptError};
15use signature::{Keypair, Signer};
16
17use crate::asn1::asn_util::is_dsa_oid;
18
19type Result<T> = std::result::Result<T, QuantCryptError>;
20/// A raw private key for use with the certificate builder
21pub struct PrivateKey {
22    /// The OID for the DSA / KEM
23    oid: String,
24    /// The key material
25    private_key: Vec<u8>,
26    /// Is it a composite key
27    is_composite: bool,
28    /// The public key.
29    public_key: Option<PublicKey>,
30}
31
32impl Signer<DsaSignature> for PrivateKey {
33    fn try_sign(&self, tbs: &[u8]) -> core::result::Result<DsaSignature, signature::Error> {
34        let sm = self.sign(tbs).map_err(|_| signature::Error::new())?;
35        Ok(DsaSignature(sm))
36    }
37}
38
39impl Keypair for PrivateKey {
40    type VerifyingKey = PublicKey;
41
42    fn verifying_key(&self) -> <Self as Keypair>::VerifyingKey {
43        // TODO: This can panic if public key is not set
44        self.public_key.clone().unwrap()
45    }
46}
47
48impl DynSignatureAlgorithmIdentifier for PrivateKey {
49    fn signature_algorithm_identifier(
50        &self,
51    ) -> core::result::Result<AlgorithmIdentifier<der::Any>, spki::Error> {
52        let oid: ObjectIdentifier = self.oid.parse().map_err(|_| spki::Error::KeyMalformed)?;
53        let spki_algorithm = AlgorithmIdentifierOwned {
54            oid,
55            parameters: None,
56        };
57        Ok(spki_algorithm)
58    }
59}
60
61impl PrivateKey {
62    /// Create a new private key
63    ///
64    /// # Arguments
65    ///
66    /// * `oid` - The OID for the DSA / KEM
67    /// * `key` - The key material
68    ///
69    /// # Returns
70    ///
71    /// A new private key
72    ///
73    /// # Errors
74    ///
75    /// `KeyError::InvalidPrivateKey` will be returned if the OID is invalid
76    pub(crate) fn new(oid: &str, key: &[u8], public_key: Option<PublicKey>) -> Result<Self> {
77        if !is_valid_kem_or_dsa_oid(&oid.to_string()) {
78            return Err(errors::QuantCryptError::InvalidPrivateKey);
79        }
80        let is_composite = is_composite_kem_or_dsa_oid(oid);
81        Ok(Self {
82            oid: oid.to_string(),
83            private_key: key.to_vec(),
84            is_composite,
85            public_key,
86        })
87    }
88
89    /// Create a new private key from a composite private key
90    ///
91    /// # Arguments
92    ///
93    /// * `public_key` - The public key (if available)
94    /// * `composite_sk` - The composite private key
95    ///
96    /// # Returns
97    ///
98    /// A new private key
99    ///
100    /// # Errors
101    ///
102    /// `KeyError::InvalidPrivateKey` will be returned if the private key is invalid
103    pub fn from_composite(
104        public_key: Option<PublicKey>,
105        composite_sk: &CompositePrivateKey,
106    ) -> Result<Self> {
107        Ok(Self {
108            oid: composite_sk.get_oid().to_string(),
109            private_key: composite_sk
110                .to_der()
111                .map_err(|_| errors::QuantCryptError::InvalidPrivateKey)?,
112            is_composite: true,
113            public_key,
114        })
115    }
116
117    /// Get the OID for the DSA / KEM
118    ///
119    /// # Returns
120    ///
121    /// The OID for the DSA / KEM
122    pub fn get_oid(&self) -> &str {
123        &self.oid
124    }
125
126    /// Get the key material
127    ///
128    /// # Returns
129    ///
130    /// The key material
131    #[cfg(test)]
132    fn get_key(&self) -> &[u8] {
133        &self.private_key
134    }
135
136    /// Get the public key
137    ///
138    /// # Returns
139    ///
140    /// The public key (if available)
141    pub fn get_public_key(&self) -> Option<&PublicKey> {
142        self.public_key.as_ref()
143    }
144
145    /// Check if the key is a composite key
146    ///
147    /// # Returns
148    ///
149    /// True if the key is a composite key, false otherwise
150    pub fn is_composite(&self) -> bool {
151        self.is_composite
152    }
153
154    /// Get the key material as a DER-encoded byte array
155    ///
156    /// # Returns
157    ///
158    /// The DER-encoded byte array
159    ///
160    /// # Errors
161    ///
162    /// `KeyError::InvalidPrivateKey` will be returned if the private key is invalid
163    pub fn to_der(&self) -> Result<Vec<u8>> {
164        let pub_key = self.public_key.as_ref().map(|pk| pk.get_key());
165
166        let oid: ObjectIdentifier = self
167            .oid
168            .parse()
169            .map_err(|_| QuantCryptError::InvalidPrivateKey)?;
170
171        let priv_key_info = PrivateKeyInfo {
172            algorithm: AlgorithmIdentifier {
173                oid,
174                parameters: None,
175            },
176            private_key: &self.private_key,
177            public_key: pub_key,
178        };
179        Ok(priv_key_info
180            .to_der()
181            .map_err(|_| errors::QuantCryptError::InvalidPrivateKey))?
182    }
183
184    /// Get the key material as a PEM-encoded string
185    ///
186    /// # Returns
187    ///
188    /// The PEM-encoded string
189    ///
190    /// # Errors
191    ///
192    /// `KeyError::InvalidPrivateKey` will be returned if the private key is invalid
193    pub fn to_pem(&self) -> Result<String> {
194        let der = self
195            .to_der()
196            .map_err(|_| errors::QuantCryptError::InvalidPrivateKey)?;
197        let pem_obj = pem::Pem::new("PRIVATE KEY", der);
198        let encode_conf = EncodeConfig::default().set_line_ending(pem::LineEnding::LF);
199        Ok(pem::encode_config(&pem_obj, encode_conf))
200    }
201
202    /// Create a new private key from a PEM-encoded string
203    ///
204    /// # Arguments
205    ///
206    /// * `pem` - The PEM-encoded string
207    ///
208    /// # Returns
209    ///
210    /// A new private key
211    ///
212    /// # Errors
213    ///
214    /// `KeyError::InvalidPrivateKey` will be returned if the private key is invalid
215    pub fn from_pem(pem: &str) -> Result<Self> {
216        let pem = pem::parse(pem).map_err(|_| errors::QuantCryptError::InvalidPrivateKey)?;
217        // Header should be "PRIVATE KEY"
218        if pem.tag() != "PRIVATE KEY" {
219            return Err(errors::QuantCryptError::InvalidPrivateKey);
220        }
221
222        let der = pem.contents();
223        Self::from_der(der)
224    }
225
226    /// Create a new private key from a DER-encoded byte array
227    ///
228    /// # Arguments
229    ///
230    /// * `der` - The DER-encoded byte array
231    ///
232    /// # Returns
233    ///
234    /// A new private key
235    ///
236    /// # Errors
237    ///
238    /// `KeyError::InvalidPrivateKey` will be returned if the private key is invalid
239    pub fn from_der(der: &[u8]) -> Result<Self> {
240        let priv_key_info = PrivateKeyInfo::from_der(der)
241            .map_err(|_| errors::QuantCryptError::InvalidPrivateKey)?;
242
243        let oid = priv_key_info.algorithm.oid.to_string();
244
245        // Check if the OID is valid
246        if !is_valid_kem_or_dsa_oid(&oid) {
247            return Err(errors::QuantCryptError::InvalidPrivateKey);
248        }
249
250        // Check if the OID is a composite key
251        let is_composite = is_composite_kem_or_dsa_oid(&oid);
252
253        // Check if the public key is present
254        let public_key = if let Some(pk) = priv_key_info.public_key {
255            Some(PublicKey::new(&oid, pk)?)
256        } else {
257            None
258        };
259
260        Ok(Self {
261            oid: oid.to_string(),
262            private_key: priv_key_info.private_key.to_vec(),
263            is_composite,
264            public_key,
265        })
266    }
267
268    /// Sign a message
269    ///
270    /// # Arguments
271    ///
272    /// * `data` - The data to sign
273    ///
274    /// # Returns
275    ///
276    /// The signature
277    pub fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
278        // Signing is only possible with DSA keys
279        if !is_dsa_oid(&self.oid) {
280            return Err(errors::QuantCryptError::UnsupportedOperation);
281        }
282
283        let dsa = DsaManager::new_from_oid(&self.oid)?;
284
285        let sig = dsa.sign(&self.private_key, data)?;
286
287        Ok(sig)
288    }
289
290    /// Use the private key to decapsulate a shared secret from a ciphertext
291    ///
292    /// # Arguments
293    ///
294    /// * `ct` - The ciphertext
295    ///
296    /// # Returns
297    ///
298    /// The shared secret
299    ///
300    /// # Errors
301    ///
302    /// `QuantCryptError::UnsupportedOperation` will be returned if this private key is not a KEM key
303    pub(crate) fn decap(&self, ct: &[u8]) -> Result<Vec<u8>> {
304        if is_dsa_oid(&self.oid) {
305            return Err(errors::QuantCryptError::UnsupportedOperation);
306        }
307        let kem = KemManager::new_from_oid(&self.oid)?;
308        let ss = kem.decap(&self.private_key, ct)?;
309        Ok(ss)
310    }
311
312    /// Load a private key from a file. The file can be in either DER or PEM format
313    ///
314    /// # Arguments
315    ///
316    /// * `path` - The path to the file
317    ///
318    /// # Returns
319    ///
320    /// The private key
321    pub fn from_file(path: &str) -> Result<Self> {
322        // Read the contents of the file as bytes
323        let contents = std::fs::read(path).map_err(|_| QuantCryptError::FileReadError)?;
324
325        // Try to interpret as DER
326        let result = PrivateKey::from_der(&contents);
327
328        if let Ok(sk) = result {
329            Ok(sk)
330        } else {
331            // Try to interpret as PEM
332            let pem =
333                std::str::from_utf8(&contents).map_err(|_| QuantCryptError::InvalidCertificate)?;
334            if let Ok(sk) = PrivateKey::from_pem(pem) {
335                Ok(sk)
336            } else {
337                Err(QuantCryptError::InvalidPrivateKey)
338            }
339        }
340    }
341
342    /// Save the private key to a file in PEM format
343    ///
344    /// # Arguments
345    ///
346    /// * `path` - The path to the file
347    ///
348    /// # Errors
349    ///
350    /// `QuantCryptError::FileWriteError` will be returned if there is an error writing to the file
351    pub fn to_pem_file(&self, path: &str) -> Result<()> {
352        let pem = self
353            .to_pem()
354            .map_err(|_| QuantCryptError::InvalidPrivateKey)?;
355        std::fs::write(path, pem).map_err(|_| QuantCryptError::FileWriteError)?;
356        Ok(())
357    }
358
359    /// Save the private key to a file in DER format
360    ///
361    /// # Arguments
362    ///
363    /// * `path` - The path to the file
364    ///
365    /// # Errors
366    ///
367    /// `QuantCryptError::FileWriteError` will be returned if there is an error writing to the file
368    pub fn to_der_file(&self, path: &str) -> Result<()> {
369        let der = self
370            .to_der()
371            .map_err(|_| QuantCryptError::InvalidPrivateKey)?;
372        std::fs::write(path, der).map_err(|_| QuantCryptError::FileWriteError)?;
373        Ok(())
374    }
375}
376
377#[cfg(test)]
378mod test {
379    use crate::dsa::common::config::oids::Oid;
380    use crate::dsa::common::dsa_type::DsaType;
381
382    use super::*;
383
384    #[test]
385    fn test_composite_private_key() {
386        let pem_bytes = include_bytes!("../../test/data/mldsa44_ecdsa_p256_sha256_sk.pem");
387        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
388        let pk = PrivateKey::from_pem(pem).unwrap();
389
390        // There is no public key in the PEM file
391        assert!(pk.public_key.is_none());
392
393        assert!(pk.is_composite());
394        assert_eq!(pk.get_oid(), DsaType::MlDsa44EcdsaP256SHA256.get_oid());
395
396        let key_bytes = pk.get_key();
397        let pk2 = CompositePrivateKey::from_der(&pk.oid, &key_bytes).unwrap();
398
399        assert_eq!(pk.oid, pk2.get_oid());
400
401        let pk2 = PrivateKey::from_composite(pk.public_key, &pk2).unwrap();
402        let pem2 = pk2.to_pem().unwrap();
403        assert_eq!(pem, pem2.trim());
404
405        let oid = DsaType::MlDsa44EcdsaP256SHA256.get_oid();
406        assert_eq!(pk.oid, oid);
407    }
408
409    #[test]
410    fn test_pk_no_headers() {
411        let pem_bytes = include_bytes!("../../test/data/bad/no_headers.pem");
412        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
413        let pk = PrivateKey::from_pem(pem);
414
415        assert!(pk.is_err());
416        assert!(matches!(
417            pk.err().unwrap(),
418            errors::QuantCryptError::InvalidPrivateKey
419        ));
420    }
421
422    #[test]
423    fn test_pk_bad_base64() {
424        let pem_bytes = include_bytes!("../../test/data/bad/bad_base64.pem");
425        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
426        let pk = PrivateKey::from_pem(pem);
427
428        assert!(pk.is_err());
429        assert!(matches!(
430            pk.err().unwrap(),
431            errors::QuantCryptError::InvalidPrivateKey
432        ));
433    }
434
435    #[test]
436    fn test_pk_empty() {
437        let pem_bytes = include_bytes!("../../test/data/bad/empty.pem");
438        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
439        let pk = PrivateKey::from_pem(pem);
440
441        assert!(pk.is_err());
442        assert!(matches!(
443            pk.err().unwrap(),
444            errors::QuantCryptError::InvalidPrivateKey
445        ));
446    }
447
448    #[test]
449    fn test_pk_bad_tag() {
450        let pem_bytes = include_bytes!("../../test/data/bad/bad_tag.pem");
451        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
452        let pk = PrivateKey::from_pem(pem);
453
454        assert!(pk.is_err());
455        assert!(matches!(
456            pk.err().unwrap(),
457            errors::QuantCryptError::InvalidPrivateKey
458        ));
459    }
460
461    #[test]
462    fn test_pk_bad_algorithm() {
463        let pem_bytes = include_bytes!("../../test/data/bad/private_rsa_2048.pem");
464        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
465        let pk = PrivateKey::from_pem(pem);
466
467        assert!(pk.is_err());
468        assert!(matches!(
469            pk.err().unwrap(),
470            errors::QuantCryptError::InvalidPrivateKey
471        ));
472    }
473
474    #[test]
475    fn test_sk_serialization_deserialization() {
476        let pem_bytes = include_bytes!("../../test/data/mldsa44_ecdsa_p256_sha256_sk.pem");
477        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
478        let pk = PrivateKey::from_pem(pem).unwrap();
479
480        let der = pk.to_der().unwrap();
481        let pk2 = PrivateKey::from_der(&der).unwrap();
482        let pem2 = pk2.to_pem().unwrap();
483        assert_eq!(pem.trim(), pem2.trim());
484
485        let der2 = pk2.to_der().unwrap();
486        assert_eq!(der, der2);
487    }
488
489    #[test]
490    fn test_sk_containing_pk() {
491        let (pk, sk) = DsaManager::new(DsaType::MlDsa44)
492            .unwrap()
493            .key_gen()
494            .unwrap();
495        let pk = PublicKey::new(&DsaType::MlDsa44.get_oid(), &pk).unwrap();
496        let sk = PrivateKey::new(&DsaType::MlDsa44.get_oid(), &sk, Some(pk.clone())).unwrap();
497        let sk_der = sk.to_der().unwrap();
498        let sk2 = PrivateKey::from_der(&sk_der).unwrap();
499        let pk2 = sk2.get_public_key().unwrap();
500        assert_eq!(pk.get_key(), pk2.get_key());
501        assert_eq!(
502            sk.get_public_key().unwrap().get_key(),
503            sk2.get_public_key().unwrap().get_key()
504        );
505    }
506}