quantcrypt/cms/
directory_cert_store.rs

1use cms::enveloped_data::RecipientIdentifier;
2use std::fs;
3use std::path::Path;
4
5use crate::cms::cert_store_trait::CertificateStore;
6use crate::{certificates::Certificate, QuantCryptError};
7
8type Result<T> = std::result::Result<T, QuantCryptError>;
9
10/// Directory-based certificate store.
11///
12/// This is a simple implementation of the `CertificateStore` trait that
13/// uses a directory of certificates.
14///
15/// The directory should contain the certificates of the trust anchors, sub-CAs,
16/// and end-entities. The certificates should be in DER or PEM format.
17///
18/// This store will attempt to resolve the recipient's certificate to a
19/// trust anchor certificate by following the certificate chain.
20///
21/// The TA certificates should be self-signed. The EE certificates should be
22/// signed by a sub-CA or a TA.
23///
24/// sub-CA certs should be signed by a TA or another sub-CA.
25///
26/// This store does not support CRLs or OCSP and does not check the path
27/// length constraints. It is meant for testing and educational purposes only.
28///
29/// For a real-world application, use you can write your own authenticator that
30/// implements the `CertificateStore` trait and can check the CRLs, OCSP,
31/// and path length constraints.
32pub struct DirectoryCertificateStore {
33    ta_certificates: Vec<Certificate>,
34    ee_certificates: Vec<Certificate>,
35}
36
37impl DirectoryCertificateStore {
38    /// Create a new directory certificate store.
39    ///
40    /// # Arguments
41    ///
42    /// * `path` - The path to the directory containing the certificates.
43    ///
44    /// # Returns
45    ///
46    /// A new `DirectoryCertificateStore` instance.
47    pub fn new(path: &str) -> Result<DirectoryCertificateStore> {
48        let mut ta_certificates = vec![];
49        let mut ee_certificates = vec![];
50
51        let dir_path = Path::new(path);
52
53        // Check if the path exists and is a directory
54        if dir_path.exists() && dir_path.is_dir() {
55            // Iterate through the directory
56            for entry in
57                fs::read_dir(dir_path).map_err(|_| QuantCryptError::InvalidDirectoryPath)?
58            {
59                let entry = entry.map_err(|_| QuantCryptError::InvalidDirectoryPath)?;
60                let path = entry.path();
61                let path_str = path.to_str().ok_or(QuantCryptError::InvalidDirectoryPath)?;
62
63                // Try to interpret the file as a certificate
64                let cert = Certificate::from_file(path_str);
65                if let Ok(cert) = cert {
66                    // Check if the certificate is self-signed
67                    let is_self_signed = cert.verify_self_signed().map_or(false, |result| result);
68
69                    let is_valid = cert.is_valid();
70
71                    // Add the certificate to the appropriate list
72                    if is_valid {
73                        if is_self_signed {
74                            ta_certificates.push(cert);
75                        } else {
76                            ee_certificates.push(cert);
77                        }
78                    }
79                }
80            }
81        } else {
82            return Err(QuantCryptError::InvalidDirectoryPath);
83        }
84
85        Ok(DirectoryCertificateStore {
86            ta_certificates,
87            ee_certificates,
88        })
89    }
90
91    /// Find the parent certificate of the given certificate.
92    fn find_parent(&self, cert: &Certificate) -> Option<Certificate> {
93        // First check if the cert is valid
94        if !cert.is_valid() {
95            return None;
96        }
97
98        // First look in the trust anchor certificates
99        for ta_cert in &self.ta_certificates {
100            if !ta_cert.is_valid() {
101                continue;
102            }
103            let cert = ta_cert.verify_child(cert).map_or(None, |result| {
104                if result {
105                    return Some(ta_cert.clone());
106                }
107                None
108            });
109            if cert.is_some() {
110                return cert;
111            }
112        }
113
114        // Then look in the end-entity certificates
115        for ee_cert in &self.ee_certificates {
116            if !ee_cert.is_valid() {
117                continue;
118            }
119            let cert = ee_cert.verify_child(cert).map_or(None, |result| {
120                if result {
121                    return self.find_parent(ee_cert);
122                }
123                None
124            });
125            if cert.is_some() {
126                return cert;
127            }
128        }
129        None
130    }
131}
132
133impl CertificateStore for DirectoryCertificateStore {
134    fn find(&self, ri: RecipientIdentifier) -> Option<Certificate> {
135        match ri {
136            cms::enveloped_data::RecipientIdentifier::IssuerAndSerialNumber(issuer) => {
137                let serial = issuer.serial_number;
138                let issuer = issuer.issuer;
139                for cert in self.ee_certificates.clone() {
140                    if cert.get_serial_number() == serial && cert.get_issuer() == issuer {
141                        // Key encipherment must be enabled
142                        if cert.is_key_encipherment_enabled() {
143                            // Find the parent certificate
144                            let parent = self.find_parent(&cert);
145                            if parent.is_some() {
146                                return Some(cert);
147                            }
148                        }
149                    }
150                }
151            }
152            cms::enveloped_data::RecipientIdentifier::SubjectKeyIdentifier(ski) => {
153                for cert in self.ee_certificates.clone() {
154                    if let Ok(cert_ski) = cert.get_subject_key_identifier() {
155                        if cert_ski == ski {
156                            // Key encipherment must be enabled
157                            if cert.is_key_encipherment_enabled() {
158                                // Find the parent certificate
159                                let parent = self.find_parent(&cert);
160                                if parent.is_some() {
161                                    return Some(cert);
162                                }
163                            }
164                        }
165                    }
166                }
167            }
168        }
169        None
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use x509_cert::builder::Profile;
176
177    use crate::{
178        certificates::{CertValidity, CertificateBuilder},
179        dsas::{DsaAlgorithm, DsaKeyGenerator},
180        kems::{KemAlgorithm, KemKeyGenerator},
181    };
182
183    use super::*;
184
185    #[test]
186    fn test_directory_recipient_auth() {
187        let dir_path = "test/data/chain";
188
189        // Create some certificates
190        let (ta_pk, ta_sk) = DsaKeyGenerator::new(DsaAlgorithm::MlDsa44)
191            .generate()
192            .unwrap();
193        let cert = CertificateBuilder::new(
194            Profile::Root,
195            None,
196            CertValidity::new(None, "2035-01-01T00:00:00Z").unwrap(),
197            "CN=example.com".to_string(),
198            ta_pk.clone(),
199            &ta_sk,
200        )
201        .unwrap()
202        .build()
203        .unwrap();
204
205        // Write the certificate to a file
206        let cert_path = format!(
207            "{}/{}_{}_ta.der",
208            dir_path,
209            cert.get_public_key_oid(),
210            cert.get_public_key_oid_friendly_name()
211        );
212        cert.to_der_file(&cert_path).unwrap();
213
214        // Create a sub-CA certificate
215        let (sub_pk, sub_sk) = DsaKeyGenerator::new(DsaAlgorithm::MlDsa44)
216            .generate()
217            .unwrap();
218        let cert_sub = CertificateBuilder::new(
219            Profile::SubCA {
220                issuer: cert.get_subject(),
221                path_len_constraint: None,
222            },
223            None,
224            CertValidity::new(None, "2035-01-01T00:00:00Z").unwrap(),
225            "CN=sub.example.com".to_string(),
226            sub_pk.clone(),
227            &ta_sk,
228        )
229        .unwrap()
230        .build()
231        .unwrap();
232
233        // Write the certificate to a file
234        let cert_path = format!(
235            "{}/{}_{}_sub.der",
236            dir_path,
237            cert_sub.get_public_key_oid(),
238            cert_sub.get_public_key_oid_friendly_name()
239        );
240        cert_sub.to_pem_file(&cert_path).unwrap();
241
242        // Create an end-entity certificate
243        let (ee_pk, _) = KemKeyGenerator::new(KemAlgorithm::MlKem512)
244            .generate()
245            .unwrap();
246        let cert_ee = CertificateBuilder::new(
247            Profile::Leaf {
248                issuer: cert_sub.get_subject(),
249                enable_key_agreement: false,
250                enable_key_encipherment: true,
251            },
252            None,
253            CertValidity::new(None, "2035-01-01T00:00:00Z").unwrap(),
254            "CN=ee.sub.example.com".to_string(),
255            ee_pk,
256            &sub_sk,
257        )
258        .unwrap()
259        .build()
260        .unwrap();
261
262        // Write the certificate to a file
263        let cert_path = format!(
264            "{}/{}_{}_ee.der",
265            dir_path,
266            cert_ee.get_public_key_oid(),
267            cert_ee.get_public_key_oid_friendly_name()
268        );
269        cert_ee.to_der_file(&cert_path).unwrap();
270
271        let auth = DirectoryCertificateStore::new(dir_path).unwrap();
272
273        // Test finding the trust anchor certificate
274        let ta_ri = RecipientIdentifier::IssuerAndSerialNumber(cms::cert::IssuerAndSerialNumber {
275            issuer: cert.get_issuer(),
276            serial_number: cert.get_serial_number(),
277        });
278
279        let ta_cert = auth.find(ta_ri);
280        // It should not be found because the key encipherment is not enabled
281        assert!(ta_cert.is_none());
282
283        // Test finding the TA certificate, by subject key identifier
284        let ta_ri =
285            RecipientIdentifier::SubjectKeyIdentifier(cert.get_subject_key_identifier().unwrap());
286        let ta_cert = auth.find(ta_ri);
287        assert!(ta_cert.is_none());
288
289        // Test finding the sub-CA certificate
290        let sub_ri = RecipientIdentifier::IssuerAndSerialNumber(cms::cert::IssuerAndSerialNumber {
291            issuer: cert_sub.get_issuer(),
292            serial_number: cert_sub.get_serial_number(),
293        });
294
295        let sub_cert = auth.find(sub_ri);
296        assert!(sub_cert.is_none());
297
298        // Test finding the sub-CA certificate, by subject key identifier
299        let sub_ri = RecipientIdentifier::SubjectKeyIdentifier(
300            cert_sub.get_subject_key_identifier().unwrap(),
301        );
302        let sub_cert = auth.find(sub_ri);
303        assert!(sub_cert.is_none());
304
305        // Test finding the end-entity certificate
306        let ee_ri = RecipientIdentifier::IssuerAndSerialNumber(cms::cert::IssuerAndSerialNumber {
307            issuer: cert_ee.get_issuer(),
308            serial_number: cert_ee.get_serial_number(),
309        });
310
311        let ee_cert = auth.find(ee_ri).unwrap();
312        assert_eq!(ee_cert.get_subject().to_string(), "CN=ee.sub.example.com");
313
314        // Test finding the end-entity certificate, by subject key identifier
315        let ee_ri = RecipientIdentifier::SubjectKeyIdentifier(
316            cert_ee.get_subject_key_identifier().unwrap(),
317        );
318        let ee_cert = auth.find(ee_ri).unwrap();
319        assert_eq!(ee_cert.get_subject().to_string(), "CN=ee.sub.example.com");
320    }
321}