quantcrypt/asn1/
cert_builder.rs

1use std::error::Error;
2use std::str::FromStr;
3
4use chrono::{DateTime, Datelike, TimeZone, Timelike};
5use pkcs8::spki::SubjectPublicKeyInfo;
6use rand::RngCore;
7use rand_core::OsRng;
8use x509_cert::builder::Builder;
9pub use x509_cert::builder::Profile;
10use x509_cert::ext::AsExtension;
11use x509_cert::time::Time;
12use x509_cert::{name::Name, serial_number::SerialNumber, time::Validity};
13
14use crate::{errors::QuantCryptError, keys::PrivateKey, keys::PublicKey};
15
16use crate::asn1::certificate::Certificate;
17
18type Result<T> = std::result::Result<T, QuantCryptError>;
19
20/// A struct to hold the validity period of a certificate
21#[derive(Clone)]
22pub struct CertValidity {
23    /// The not before date of the certificate
24    pub not_before: der::asn1::UtcTime,
25    /// The not after date of the certificate
26    pub not_after: der::asn1::UtcTime,
27}
28
29impl CertValidity {
30    fn date_time_to_asn(
31        time: &DateTime<chrono::Utc>,
32    ) -> std::result::Result<der::asn1::UtcTime, Box<dyn Error>> {
33        let dt = der::DateTime::new(
34            time.year() as u16,
35            time.month() as u8,
36            time.day() as u8,
37            time.hour() as u8,
38            time.minute() as u8,
39            time.second() as u8,
40        )?;
41        let result = der::asn1::UtcTime::from_date_time(dt)?;
42        Ok(result)
43    }
44
45    /// Create a new CertValidity struct
46    ///
47    /// # Arguments
48    ///
49    /// * `not_before` - The not before date of the certificate. If None, the current time is used. The date should be in RFC3339 format.
50    /// * `not_after` - The not after date of the certificate. The date should be in RFC3339 format.
51    ///
52    /// # Returns
53    ///
54    /// A new CertValidity struct
55    ///
56    /// # Errors
57    ///
58    /// `QuantCryptError::InvalidNotBefore` if the not before date is in the future
59    /// `QuantCryptError::InvalidNotAfter` if the not after date is in the past
60    pub fn new(not_before: Option<&str>, not_after: &str) -> Result<CertValidity> {
61        let not_after = DateTime::parse_from_rfc3339(not_after)
62            .map_err(|_| QuantCryptError::InvalidNotAfter)?;
63
64        // Set time to UTC
65        let not_after = chrono::Utc.from_utc_datetime(&not_after.naive_utc());
66
67        // Check if not after is in the past
68        if not_after <= chrono::Utc::now() {
69            return Err(QuantCryptError::InvalidNotAfter);
70        }
71
72        let not_after_dt = CertValidity::date_time_to_asn(&not_after)
73            .map_err(|_| QuantCryptError::InvalidNotAfter)?;
74
75        if let Some(not_before) = not_before {
76            let not_before = DateTime::parse_from_rfc3339(not_before)
77                .map_err(|_| QuantCryptError::InvalidNotBefore)?;
78
79            // Set time to UTC
80            let not_before = chrono::Utc.from_utc_datetime(&not_before.naive_utc());
81
82            if not_before > not_after {
83                return Err(QuantCryptError::InvalidNotBefore);
84            }
85
86            let not_before_dt = CertValidity::date_time_to_asn(&not_before)
87                .map_err(|_| QuantCryptError::InvalidNotBefore)?;
88
89            Ok(CertValidity {
90                not_before: not_before_dt,
91                not_after: not_after_dt,
92            })
93        } else {
94            // Use now as not_before
95            let not_before = chrono::Utc::now();
96
97            if not_before > not_after {
98                return Err(QuantCryptError::InvalidNotAfter);
99            }
100
101            let not_before_dt = CertValidity::date_time_to_asn(&not_before)
102                .map_err(|_| QuantCryptError::InvalidNotBefore)?;
103
104            Ok(CertValidity {
105                not_before: not_before_dt,
106                not_after: not_after_dt,
107            })
108        }
109    }
110}
111
112/// A builder for creating X.509 certificates
113///
114/// # Example:
115/// ```
116/// use quantcrypt::certificates::CertificateBuilder;
117/// use quantcrypt::dsas::DsaAlgorithm;
118/// use quantcrypt::kems::KemAlgorithm;
119/// use quantcrypt::certificates::Profile;
120/// use quantcrypt::dsas::DsaKeyGenerator;
121/// use quantcrypt::kems::KemKeyGenerator;
122/// use quantcrypt::certificates::CertValidity;
123///
124/// // Create a TA key pair
125/// let (pk_root, sk_root) = DsaKeyGenerator::new(DsaAlgorithm::MlDsa44).generate().unwrap();
126///
127/// let profile = Profile::Root;
128/// let serial_no = None; // This will generate a random serial number
129/// let validity = CertValidity::new(None, "2035-01-01T00:00:00Z").unwrap(); // Not before is now
130/// let subject = "CN=example.com".to_string();
131/// let cert_public_key = pk_root.clone();
132/// let signer = &sk_root;
133///
134/// // Create the TA certificate builder
135/// let builder = CertificateBuilder::new(
136///   profile,
137///   serial_no,
138///   validity.clone(),
139///   subject.clone(),
140///   cert_public_key,
141///   signer).unwrap();
142/// let cert_root = builder.build().unwrap();
143/// assert!(cert_root.verify_self_signed().unwrap());
144/// // Create a leaf (EE) key pair for KEM
145/// let (pk_kem, sk_kem) = KemKeyGenerator::new(KemAlgorithm::MlKem512).generate().unwrap();
146/// let builder = CertificateBuilder::new(Profile::Leaf {
147///   issuer: cert_root.get_subject(),
148///   enable_key_agreement: false,
149///   enable_key_encipherment: true,
150/// }, serial_no,
151///   validity,
152///   subject,
153///   pk_kem,
154///   signer).unwrap();
155/// let cert_kem = builder.build().unwrap();
156///
157/// // It's not self signed so verification as self signed should fail
158/// assert!(!cert_kem.verify_self_signed().unwrap());
159///
160/// // But it should verify against the root
161/// assert!(cert_root.verify_child(&cert_kem).unwrap());
162/// ```
163pub struct CertificateBuilder<'a> {
164    builder: x509_cert::builder::CertificateBuilder<'a, PrivateKey>,
165}
166
167impl<'a> CertificateBuilder<'a> {
168    /// Create a new certificate builder
169    pub fn new(
170        profile: Profile,
171        serial_number: Option<[u8; 20]>,
172        validity: CertValidity,
173        subject: String,
174        cert_public_key: PublicKey,
175        signer: &'a PrivateKey,
176    ) -> Result<CertificateBuilder<'a>> {
177        let subject = Name::from_str(&subject).map_err(|_| QuantCryptError::BadSubject)?;
178
179        let spki = SubjectPublicKeyInfo::from_key(cert_public_key)
180            .map_err(|_| QuantCryptError::BadPublicKey)?;
181
182        let validity = Validity {
183            not_before: Time::UtcTime(validity.not_before),
184            not_after: Time::UtcTime(validity.not_after),
185        };
186
187        let serial_number = if let Some(serial_number) = serial_number {
188            SerialNumber::new(&serial_number).map_err(|_| QuantCryptError::BadSerialNumber)?
189        } else {
190            CertificateBuilder::get_random_serial()?
191        };
192
193        let builder = x509_cert::builder::CertificateBuilder::new(
194            profile,
195            serial_number,
196            validity,
197            subject,
198            spki,
199            signer,
200        )
201        .map_err(|_| QuantCryptError::Unknown)?;
202
203        Ok(CertificateBuilder { builder })
204    }
205
206    pub fn add_extension(&mut self, extension: impl AsExtension) -> Result<&mut Self> {
207        self.builder
208            .add_extension(&extension)
209            .map_err(|_| QuantCryptError::BadExtension)?;
210
211        Ok(self)
212    }
213
214    /// Return a random SerialNumber value
215    fn get_random_serial() -> Result<SerialNumber> {
216        let mut serial = [0u8; 20];
217        OsRng.fill_bytes(&mut serial);
218        serial[0] = 0x01;
219        let serial = SerialNumber::new(&serial).map_err(|_| QuantCryptError::BadSerialNumber)?;
220        Ok(serial)
221    }
222
223    pub fn build(self) -> Result<Certificate> {
224        let cert_inner = self.builder.build().map_err(|_| QuantCryptError::Unknown)?;
225        let cert = Certificate::new(cert_inner);
226        Ok(cert)
227    }
228}
229
230#[cfg(test)]
231mod test {
232
233    use crate::{dsas::DsaAlgorithm, dsas::DsaKeyGenerator};
234
235    use super::*;
236
237    #[test]
238    fn gen_pq_hackathon_artifacts_r4() {
239        // Generate R4 artifacts for the hackathon
240        let dsa_algs: Vec<DsaAlgorithm> = DsaAlgorithm::all();
241
242        for dsa_alg in dsa_algs.iter() {
243            // Use DSA to generate key pair for Trust authority
244            let (pk_root, sk_root) = DsaKeyGenerator::new(*dsa_alg).generate().unwrap();
245
246            let profile = Profile::Root;
247            let serial_no = None; // This will generate a random serial number
248            let validity = CertValidity::new(None, "2034-01-01T00:00:00Z").unwrap(); // Not before is now
249            let subject = "CN=example.com".to_string();
250            let cert_public_key = pk_root.clone();
251            let signer = &sk_root;
252
253            // Create the TA certificate builder
254            // This is a self-signed certificate since cert_public_key and signer are both from the root
255            let builder = CertificateBuilder::new(
256                profile,
257                serial_no,
258                validity.clone(),
259                subject.clone(),
260                cert_public_key,
261                signer,
262            )
263            .unwrap();
264            let cert_root = builder.build().unwrap();
265
266            // Verify self-sign cert
267            assert!(cert_root.verify_self_signed().unwrap());
268
269            let dsa_alg_name = dsa_alg.to_string();
270
271            let mut save_dir = "artifacts/r4_certs/non-ipd";
272
273            // If IPD mode is enabled, prefix IPD_ to the filename
274            if crate::is_ipd_mode_enabled() {
275                save_dir = "artifacts/r4_certs/ipd";
276            }
277
278            let file_name = format!("{}/{}-{}_ta.der", save_dir, dsa_alg_name, dsa_alg.get_oid());
279
280            // // Write the self-signed certificate from TA to the temp directory
281            cert_root.to_der_file(&file_name).unwrap();
282        }
283    }
284
285    #[test]
286    fn gen_pq_hackathon_artifacts_r3() {
287        // Generate R3 artifacts for the hackathon
288        let dsa_algs: Vec<DsaAlgorithm> = DsaAlgorithm::all();
289
290        for dsa_alg in dsa_algs.iter() {
291            // Use DSA to generate key pair for Trust authority
292            let (pk_root, sk_root) = DsaKeyGenerator::new(*dsa_alg).generate().unwrap();
293
294            let profile = Profile::Root;
295            let serial_no = None; // This will generate a random serial number
296            let validity = CertValidity::new(None, "2034-01-01T00:00:00Z").unwrap(); // Not before is now
297            let subject = "CN=example.com".to_string();
298            let cert_public_key = pk_root.clone();
299            let signer = &sk_root;
300
301            // Create the TA certificate builder
302            // This is a self-signed certificate since cert_public_key and signer are both from the root
303            let builder = CertificateBuilder::new(
304                profile,
305                serial_no,
306                validity.clone(),
307                subject.clone(),
308                cert_public_key,
309                signer,
310            )
311            .unwrap();
312            let cert_root = builder.build().unwrap();
313
314            // Verify self-sign cert
315            assert!(cert_root.verify_self_signed().unwrap());
316
317            let mut save_dir = "artifacts/r3_certs/non-ipd";
318
319            // If IPD mode is enabled, reset save path to ipd
320            if crate::is_ipd_mode_enabled() {
321                save_dir = "artifacts/r3_certs/ipd";
322            }
323
324            let file_name = format!("{}/{}_ta.pem", save_dir, dsa_alg.get_oid());
325
326            // // Write the self-signed certificate from TA to the temp directory
327            cert_root.to_pem_file(&file_name).unwrap();
328        }
329    }
330}