Skip to main content

cert_helper/
certificate.rs

1use chrono::{NaiveDate, NaiveDateTime, TimeZone, Utc};
2use foreign_types::ForeignType;
3use openssl::asn1::{Asn1Object, Asn1OctetString, Asn1Time};
4use openssl::bn::BigNum;
5use openssl::ec::{EcGroup, EcKey};
6use openssl::error::ErrorStack;
7use openssl::hash::{MessageDigest, hash};
8use openssl::nid::Nid;
9use openssl::pkey::{Id, PKey, Private};
10use openssl::rsa::Rsa;
11use openssl::stack::Stack;
12use openssl::x509::extension::{
13    AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName,
14};
15use openssl::x509::{
16    X509, X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder, X509StoreContext,
17    store::X509StoreBuilder,
18};
19use std::collections::{HashMap, HashSet};
20use std::fs::{File, create_dir_all};
21use std::io::Write;
22use std::path::Path;
23use x509_parser::certification_request::X509CertificationRequest;
24use x509_parser::extensions::ParsedExtension;
25use x509_parser::parse_x509_certificate;
26use x509_parser::prelude::FromDer;
27use yasna::models::ObjectIdentifier;
28
29unsafe extern "C" {
30    pub fn X509_sign(
31        x: *mut openssl_sys::X509,
32        pkey: *mut openssl_sys::EVP_PKEY,
33        md: *const openssl_sys::EVP_MD,
34    ) -> ::std::os::raw::c_int;
35    pub fn X509_sign_ctx(
36        x: *mut openssl_sys::X509,
37        ctx: *mut openssl_sys::EVP_MD_CTX,
38    ) -> ::std::os::raw::c_int;
39}
40
41unsafe extern "C" {
42    pub fn X509_REQ_sign(
43        req: *mut openssl_sys::X509_REQ,
44        pkey: *mut openssl_sys::EVP_PKEY,
45        md: *const openssl_sys::EVP_MD,
46    ) -> ::std::os::raw::c_int;
47    pub fn X509_REQ_sign_ctx(
48        req: *mut openssl_sys::X509_REQ,
49        ctx: *mut openssl_sys::EVP_MD_CTX,
50    ) -> ::std::os::raw::c_int;
51}
52
53/// A certificate policy OID found in the `certificatePolicies` extension.
54///
55/// The named variants are the CA/Browser Forum reserved policy identifiers
56/// (arc `2.23.140.1`) that signal the validation level a publicly-trusted CA
57/// performed before issuance, plus the special `anyPolicy` OID from RFC 5280.
58/// Anything outside that set is preserved verbatim in [`CertificatePolicy::Other`].
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum CertificatePolicy {
61    DomainValidated,       // 2.23.140.1.2.1
62    OrganizationValidated, // 2.23.140.1.2.2
63    IndividualValidated,   // 2.23.140.1.2.3
64    ExtendedValidation,    // 2.23.140.1.1
65    AnyPolicy,             // 2.5.29.32.0
66    Other(String),         // private / arbitrary / test OID
67}
68
69impl CertificatePolicy {
70    /// Returns the policy's OID in dotted-decimal notation.
71    ///
72    /// For named variants this is a fixed `&'static str`; for
73    /// [`CertificatePolicy::Other`] it borrows the stored OID string.
74    pub fn oid(&self) -> &str {
75        match self {
76            Self::DomainValidated => "2.23.140.1.2.1",
77            Self::OrganizationValidated => "2.23.140.1.2.2",
78            Self::IndividualValidated => "2.23.140.1.2.3",
79            Self::ExtendedValidation => "2.23.140.1.1",
80            Self::AnyPolicy => "2.5.29.32.0",
81            Self::Other(oid) => oid,
82        }
83    }
84}
85/// Sign a just-built `X509` in-place with a digest-less key (Ed25519 or PQC).
86///
87/// We avoid `X509_sign(x, pkey, NULL)` because OpenSSL 3.5+ infers a default
88/// digest for ML-DSA/SLH-DSA in that path, which their providers then reject.
89/// Instead we initialise an `EVP_MD_CTX` with an explicit NULL `mdname` and
90/// hand it to `X509_sign_ctx`.
91/// RAII guard that frees an `EVP_MD_CTX` on drop, including on early return and
92/// unwind. Keeps the digest-less signing paths leak-free without manual
93/// `EVP_MD_CTX_free` on every branch. Mirrors `pqc::PkeyCtx`.
94struct MdCtx(*mut openssl_sys::EVP_MD_CTX);
95
96impl Drop for MdCtx {
97    fn drop(&mut self) {
98        if !self.0.is_null() {
99            // SAFETY: freed exactly once (on every path including unwind);
100            // EVP_MD_CTX_free is a no-op on NULL.
101            unsafe { openssl_sys::EVP_MD_CTX_free(self.0) }
102        }
103    }
104}
105
106/// Sign a just-built `X509` in-place with a digest-less key (Ed25519 or PQC).
107///
108/// Ed25519 uses the plain `X509_sign(x, pkey, NULL)` path that has always
109/// worked. PQC keys (ML-DSA / SLH-DSA) need a workaround on OpenSSL 3.5+:
110/// `X509_sign(_, _, NULL)` triggers default-digest inference in
111/// `do_sigver_init`, and the PQC providers then reject the inferred digest
112/// with "Explicit digest not supported". We instead initialise an `EVP_MD_CTX`
113/// with an *empty* C string as `mdname` — that bypasses the default-digest
114/// lookup inside OpenSSL while still satisfying the provider's
115/// `mdname[0] != '\0'` guard — and hand the ctx to `X509_sign_ctx`.
116fn sign_certificate_digestless(
117    cert: &X509,
118    pkey: &PKey<openssl::pkey::Private>,
119) -> Result<(), String> {
120    if !is_digestless_key(pkey) {
121        return Err("sign_certificate_digestless called with non-digestless key".to_string());
122    }
123    let cert_ptr = cert.as_ptr();
124    let pkey_ptr = pkey.as_ptr();
125
126    if pkey.id() == Id::ED25519 {
127        let result = unsafe { X509_sign(cert_ptr, pkey_ptr, std::ptr::null()) };
128        return if result > 0 {
129            Ok(())
130        } else {
131            Err("Failed to sign certificate with Ed25519".to_string())
132        };
133    }
134
135    // PQC path: EVP_DigestSignInit (non-ex) with NULL mdname + X509_sign_ctx.
136    // `Signer::new_without_digest` in the openssl crate uses this exact call and
137    // it works for ML-DSA/SLH-DSA whereas `EVP_DigestSignInit_ex` does not.
138    // SAFETY: `ctx` is owned by `MdCtx` and freed on every path (early return or
139    // scope exit). The internal EVP_PKEY_CTX created by EVP_DigestSignInit (NULL
140    // pctx arg) is owned by `ctx` and released with it. `pkey_ptr`/`cert_ptr` are
141    // borrows from live wrappers and are not freed here.
142    let ctx = MdCtx(unsafe { openssl_sys::EVP_MD_CTX_new() });
143    if ctx.0.is_null() {
144        return Err("EVP_MD_CTX_new returned NULL".to_string());
145    }
146    let init = unsafe {
147        openssl_sys::EVP_DigestSignInit(
148            ctx.0,
149            std::ptr::null_mut(),
150            std::ptr::null(),
151            std::ptr::null_mut(),
152            pkey_ptr,
153        )
154    };
155    if init <= 0 {
156        return Err("EVP_DigestSignInit failed for PQC key".to_string());
157    }
158    let result = unsafe { X509_sign_ctx(cert_ptr, ctx.0) };
159
160    if result > 0 {
161        Ok(())
162    } else {
163        Err("X509_sign_ctx failed for PQC key".to_string())
164    }
165}
166
167/// Same as `sign_certificate_digestless` but for `X509Req`. See the
168/// `sign_certificate_digestless` docstring for why Ed25519 and PQC take
169/// different OpenSSL paths.
170fn sign_x509_req_digestless(req: &X509Req, pkey: &PKey<Private>) -> Result<(), String> {
171    if !is_digestless_key(pkey) {
172        return Err("sign_x509_req_digestless called with non-digestless key".to_string());
173    }
174    let req_ptr = req.as_ptr();
175    let pkey_ptr = pkey.as_ptr();
176
177    if pkey.id() == Id::ED25519 {
178        let result = unsafe { X509_REQ_sign(req_ptr, pkey_ptr, std::ptr::null()) };
179        return if result > 0 {
180            Ok(())
181        } else {
182            Err("Failed to sign X509Req with Ed25519".to_string())
183        };
184    }
185
186    // SAFETY: same invariants as in `sign_certificate_digestless` — `ctx` is
187    // owned by `MdCtx` and freed on every path; `pkey_ptr`/`req_ptr` are borrows.
188    let ctx = MdCtx(unsafe { openssl_sys::EVP_MD_CTX_new() });
189    if ctx.0.is_null() {
190        return Err("EVP_MD_CTX_new returned NULL".to_string());
191    }
192    let init = unsafe {
193        openssl_sys::EVP_DigestSignInit(
194            ctx.0,
195            std::ptr::null_mut(),
196            std::ptr::null(),
197            std::ptr::null_mut(),
198            pkey_ptr,
199        )
200    };
201    if init <= 0 {
202        return Err("EVP_DigestSignInit failed for PQC key".to_string());
203    }
204    let result = unsafe { X509_REQ_sign_ctx(req_ptr, ctx.0) };
205
206    if result > 0 {
207        Ok(())
208    } else {
209        Err("X509_REQ_sign_ctx failed for PQC key".to_string())
210    }
211}
212
213/// Returns true for keys whose OpenSSL EVP signing path does not take an
214/// external digest. Today: Ed25519 and (when the `pqc` feature is enabled)
215/// the six FIPS 204 / 205 post-quantum variants.
216pub(crate) fn is_digestless_key(pkey: &PKey<Private>) -> bool {
217    if pkey.id() == Id::ED25519 {
218        return true;
219    }
220    #[cfg(feature = "pqc")]
221    {
222        return is_pqc_pkey(pkey);
223    }
224    #[allow(unreachable_code)]
225    false
226}
227
228#[cfg(feature = "pqc")]
229fn is_pqc_pkey(pkey: &PKey<Private>) -> bool {
230    use std::ffi::CString;
231    use std::sync::OnceLock;
232
233    // Cache the CStrings so we don't rebuild them per call.
234    static NAMES: OnceLock<[CString; 6]> = OnceLock::new();
235    let names = NAMES.get_or_init(|| {
236        [
237            CString::new("ML-DSA-44").unwrap(),
238            CString::new("ML-DSA-65").unwrap(),
239            CString::new("ML-DSA-87").unwrap(),
240            CString::new("SLH-DSA-SHA2-128s").unwrap(),
241            CString::new("SLH-DSA-SHA2-192s").unwrap(),
242            CString::new("SLH-DSA-SHA2-256s").unwrap(),
243        ]
244    });
245    use foreign_types::ForeignType;
246    let ptr = pkey.as_ptr();
247    names
248        .iter()
249        // SAFETY: EVP_PKEY_is_a accepts any NUL-terminated C string and a
250        // valid EVP_PKEY*; returns 0 for mismatch, 1 for match — never UB.
251        .any(|n| unsafe { pqc::EVP_PKEY_is_a(ptr, n.as_ptr()) } == 1)
252}
253
254/// FIPS 203 ML-KEM algorithm OIDs, arc `2.16.840.1.101.3.4.4.x`. These are the
255/// `id-alg-ml-kem-*` identifiers from draft-ietf-lamps-kyber-certificates that
256/// appear in an ML-KEM `SubjectPublicKeyInfo`. Detection in [`is_mlkem_pkey`] is
257/// by OpenSSL EVP algorithm name (provider-agnostic, like [`is_pqc_pkey`]); the
258/// OIDs are kept here for reference since `oid-registry` does not know them yet.
259#[cfg(feature = "pqc")]
260#[allow(dead_code)]
261const ML_KEM_OIDS: [&str; 3] = [
262    "2.16.840.1.101.3.4.4.1", // id-alg-ml-kem-512
263    "2.16.840.1.101.3.4.4.2", // id-alg-ml-kem-768
264    "2.16.840.1.101.3.4.4.3", // id-alg-ml-kem-1024
265];
266
267/// Returns true if `pkey` is an ML-KEM (FIPS 203) key-encapsulation key.
268///
269/// This is deliberately separate from [`is_pqc_pkey`]: ML-KEM keys are *not*
270/// signature keys. Per draft-ietf-lamps-kyber-certificates they may only assert
271/// the `keyEncipherment` KeyUsage bit, and they cannot produce signatures — so
272/// they can neither self-sign a certificate nor sign a CSR. Keeping them out of
273/// `is_pqc_pkey` also keeps them out of [`is_digestless_key`], which gates the
274/// signing path.
275#[cfg(feature = "pqc")]
276fn is_mlkem_pkey(pkey: &PKey<Private>) -> bool {
277    use std::ffi::CString;
278    use std::sync::OnceLock;
279
280    // Cache the CStrings so we don't rebuild them per call.
281    static NAMES: OnceLock<[CString; 3]> = OnceLock::new();
282    let names = NAMES.get_or_init(|| {
283        [
284            CString::new("ML-KEM-512").unwrap(),
285            CString::new("ML-KEM-768").unwrap(),
286            CString::new("ML-KEM-1024").unwrap(),
287        ]
288    });
289    use foreign_types::ForeignType;
290    let ptr = pkey.as_ptr();
291    names
292        .iter()
293        // SAFETY: EVP_PKEY_is_a accepts any NUL-terminated C string and a
294        // valid EVP_PKEY*; returns 0 for mismatch, 1 for match — never UB.
295        .any(|n| unsafe { pqc::EVP_PKEY_is_a(ptr, n.as_ptr()) } == 1)
296}
297
298#[cfg(feature = "pqc")]
299mod pqc {
300    use foreign_types::ForeignType;
301    use openssl::error::ErrorStack;
302    use openssl::pkey::{PKey, Private};
303    use std::ffi::CString;
304
305    unsafe extern "C" {
306        fn EVP_PKEY_CTX_new_from_name(
307            libctx: *mut std::ffi::c_void,
308            name: *const std::os::raw::c_char,
309            propquery: *const std::os::raw::c_char,
310        ) -> *mut openssl_sys::EVP_PKEY_CTX;
311        fn EVP_PKEY_keygen_init(ctx: *mut openssl_sys::EVP_PKEY_CTX) -> std::os::raw::c_int;
312        fn EVP_PKEY_generate(
313            ctx: *mut openssl_sys::EVP_PKEY_CTX,
314            ppkey: *mut *mut openssl_sys::EVP_PKEY,
315        ) -> std::os::raw::c_int;
316        fn EVP_PKEY_CTX_free(ctx: *mut openssl_sys::EVP_PKEY_CTX);
317        /// Returns 1 if `pkey` is of algorithm `name`, 0 otherwise.
318        /// Use this instead of `EVP_PKEY_id` for provider-only algorithms
319        /// (ML-DSA, SLH-DSA) whose legacy NID is -1.
320        pub fn EVP_PKEY_is_a(
321            pkey: *mut openssl_sys::EVP_PKEY,
322            name: *const std::os::raw::c_char,
323        ) -> std::os::raw::c_int;
324    }
325
326    /// RAII guard that frees an `EVP_PKEY_CTX` on drop, including unwinds.
327    struct PkeyCtx(*mut openssl_sys::EVP_PKEY_CTX);
328
329    impl Drop for PkeyCtx {
330        fn drop(&mut self) {
331            if !self.0.is_null() {
332                unsafe { EVP_PKEY_CTX_free(self.0) }
333            }
334        }
335    }
336
337    /// Generate a post-quantum signing key by OpenSSL EVP algorithm name.
338    ///
339    /// Accepts the FIPS 204 / FIPS 205 canonical names:
340    /// `"ML-DSA-44"`, `"ML-DSA-65"`, `"ML-DSA-87"`,
341    /// `"SLH-DSA-SHA2-128s"`, `"SLH-DSA-SHA2-192s"`, `"SLH-DSA-SHA2-256s"`.
342    ///
343    /// Returns `Err(ErrorStack)` if the algorithm is unknown to the linked
344    /// OpenSSL, keygen init fails, or key generation fails. Never panics,
345    /// never leaks the `EVP_PKEY_CTX`.
346    pub(super) fn generate_pqc_key(alg_name: &str) -> Result<PKey<Private>, ErrorStack> {
347        let cname = CString::new(alg_name).expect("alg_name contains interior NUL");
348
349        // SAFETY: NULL libctx => default library context. NULL propquery matches
350        // every provider. The returned ctx is owned by PkeyCtx, freed on all paths.
351        let ctx_ptr = unsafe {
352            EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), cname.as_ptr(), std::ptr::null())
353        };
354        if ctx_ptr.is_null() {
355            return Err(ErrorStack::get());
356        }
357        let ctx = PkeyCtx(ctx_ptr);
358
359        if unsafe { EVP_PKEY_keygen_init(ctx.0) } <= 0 {
360            return Err(ErrorStack::get());
361        }
362
363        let mut pkey_ptr: *mut openssl_sys::EVP_PKEY = std::ptr::null_mut();
364        if unsafe { EVP_PKEY_generate(ctx.0, &mut pkey_ptr) } <= 0 {
365            return Err(ErrorStack::get());
366        }
367        if pkey_ptr.is_null() {
368            return Err(ErrorStack::get());
369        }
370
371        // SAFETY: EVP_PKEY_generate returned ownership of a freshly-allocated
372        // EVP_PKEY. PKey::from_ptr takes ownership and frees on drop.
373        Ok(unsafe { PKey::<Private>::from_ptr(pkey_ptr) })
374    }
375}
376
377#[cfg(feature = "pqc")]
378use pqc::generate_pqc_key;
379
380macro_rules! vec_str_to_hs {
381    ($vec:expr) => {
382        $vec.iter()
383            .map(|s| s.to_string())
384            .collect::<HashSet<String>>()
385    };
386}
387/// Defines what type of key that can be used with the certificate
388#[derive(Debug, Clone, PartialEq)]
389pub enum KeyType {
390    /// RSA key with a 2048-bit length.
391    RSA2048,
392    /// RSA key with a 4096-bit length.
393    RSA4096,
394    /// Elliptic Curve key using the NIST P-224 curve (secp224r1).
395    P224,
396    /// Elliptic Curve key using the NIST P-256 curve (secp256r1). Also known as prime256v1.
397    P256,
398    /// Elliptic Curve key using the NIST P-384 curve (secp384r1).
399    P384,
400    /// Elliptic Curve key using the NIST P-521 curve (secp521r1).
401    P521,
402    /// Edwards-curve Digital Signature Algorithm using Ed25519.
403    Ed25519,
404    /// ML-DSA-44 (FIPS 204, formerly Dilithium2). Post-quantum lattice signature.
405    #[cfg(feature = "pqc")]
406    MlDsa44,
407    /// ML-DSA-65 (FIPS 204, formerly Dilithium3). Post-quantum lattice signature.
408    #[cfg(feature = "pqc")]
409    MlDsa65,
410    /// ML-DSA-87 (FIPS 204, formerly Dilithium5). Post-quantum lattice signature.
411    #[cfg(feature = "pqc")]
412    MlDsa87,
413    /// SLH-DSA-SHA2-128s (FIPS 205, formerly SPHINCS+). Hash-based signature, small variant.
414    #[cfg(feature = "pqc")]
415    SlhDsaSha2_128s,
416    /// SLH-DSA-SHA2-192s (FIPS 205). Hash-based signature, medium variant.
417    #[cfg(feature = "pqc")]
418    SlhDsaSha2_192s,
419    /// SLH-DSA-SHA2-256s (FIPS 205). Hash-based signature, large variant.
420    #[cfg(feature = "pqc")]
421    SlhDsaSha2_256s,
422    /// ML-KEM-512 (FIPS 203, formerly Kyber). Post-quantum key-encapsulation
423    /// key. Encapsulation/encryption only — cannot sign. See [`KeyType`] notes
424    /// on ML-KEM: only `keyEncipherment` is a valid KeyUsage and certificates
425    /// must be issued by a separate signing CA, not self-signed.
426    #[cfg(feature = "pqc")]
427    MlKem512,
428    /// ML-KEM-768 (FIPS 203). Post-quantum key-encapsulation key. See
429    /// [`KeyType::MlKem512`].
430    #[cfg(feature = "pqc")]
431    MlKem768,
432    /// ML-KEM-1024 (FIPS 203). Post-quantum key-encapsulation key. See
433    /// [`KeyType::MlKem512`].
434    #[cfg(feature = "pqc")]
435    MlKem1024,
436}
437/// Defines which hash algorithm to be used in certificate signing
438#[derive(Debug, Clone)]
439pub enum HashAlg {
440    /// SHA-1 (Secure Hash Algorithm 1), now considered weak and generally discouraged for new certificates.
441    SHA1,
442    /// SHA-256 (part of SHA-2 family)
443    SHA256,
444    /// SHA-384 (SHA-2 family), offers stronger security and is often used with larger key sizes.
445    SHA384,
446    /// SHA-512 (SHA-2 family), provides the highest bit-length hash in the SHA-2 family.
447    SHA512,
448}
449/// Represents the allowed usages for a certificate, used in KeyUsage and ExtendedKeyUsage extensions.
450#[allow(non_camel_case_types)]
451#[derive(Hash, Eq, PartialEq, Debug, Clone)]
452pub enum Usage {
453    /// Allows the certificate to sign other certificates (typically used for CA certificates).
454    certsign,
455    /// Allows the certificate to sign certificate revocation lists (CRLs).
456    crlsign,
457    /// Allows the certificate to be used for encrypting data (e.g., key encipherment).
458    encipherment,
459    /// Indicates the certificate can be used for client authentication in TLS.
460    clientauth,
461    /// Indicates the certificate can be used for server authentication in TLS.
462    serverauth,
463    /// Allows the certificate to be used for digital signatures.
464    signature,
465    /// Indicates the certificate can be used for content commitment (non-repudiation).
466    contentcommitment,
467}
468
469/// Common functionality for extracting PEM-encoded data and private keys from X509-related types
470pub trait X509Parts {
471    /// Returns the PEM-encoded representation of the X.509 object (e.g., certificate or CSR).
472    ///
473    /// # Returns
474    /// A `Vec<u8>` containing the PEM data, or an error if encoding fails.
475    fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>>;
476    /// Returns the PEM-encoded private key associated with the X.509 object.
477    ///
478    /// # Returns
479    /// A `Vec<u8>` containing the PEM-encoded private key, or an error if retrieval fails.
480    fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>>;
481    /// Returns the file extension typically used for the PEM output (e.g., `_cert.pem.`, `_csr.pem`, `_peky.pem`).
482    ///
483    /// # Returns
484    /// A static string slice representing the file extension.
485    fn pem_extension(&self) -> &'static str;
486}
487
488/// Provides a method to save the private key and X509 certificate or CSR data to files.
489pub trait X509Common {
490    /// Saves the X.509 object (e.g., certificate, CSR, or private key) to a file.
491    ///
492    /// # Arguments
493    /// * `path` - The directory path where the file should be saved.
494    /// * `filename` - The name of the file (without extension).
495    ///
496    /// The file extension is typically determined by the object's type (e.g., `.crt`, `.csr`, `.key`)
497    /// and is provided by the [`X509Parts::pem_extension`] method if implemented.
498    ///
499    /// # Returns
500    /// * `Ok(())` if the file was successfully written.
501    /// * `Err` if an error occurred during file creation or writing.
502    fn save<P: AsRef<Path>, F: AsRef<Path>>(
503        &self,
504        path: P,
505        filename: F,
506    ) -> Result<(), Box<dyn std::error::Error>>;
507}
508
509/// Implements `X509Common` for all types that implement `X509Parts`.
510///
511/// # Example
512/// ```no_run
513/// use cert_helper::certificate::{Certificate, X509Common};
514/// let cert = Certificate::load_cert_and_key("cert.pem", "key.pem").expect("Failed to generate certificate");
515/// cert.save("output", "mycert");
516/// ```
517impl<T: X509Parts> X509Common for T {
518    /// Will save the cert/csr  and private key to pem file
519    /// if path = /path/foo/bar and filename = mytest
520    /// For example with certificate it will be:
521    /// /path/foo/bar/mytest_cert.pem
522    /// /path/foo/bar/mytest_pkey.pem
523    /// and for certificate signing request:
524    /// /path/foo/bar/mytest_csr.pem
525    /// /path/foo/bar/mytest_pkey.pem
526    ///
527    /// If the path do not exist it will be created
528    fn save<P: AsRef<Path>, F: AsRef<Path>>(
529        &self,
530        path: P,
531        filename: F,
532    ) -> Result<(), Box<dyn std::error::Error>> {
533        create_dir_all(&path)?;
534
535        let os_file = filename
536            .as_ref()
537            .file_name()
538            .ok_or("Failed to extract file name")?;
539
540        let write_file = |suffix: &str, content: &[u8]| -> Result<(), Box<dyn std::error::Error>> {
541            let mut new_name = os_file.to_os_string();
542            new_name.push(suffix);
543            let full_path = path.as_ref().join(new_name);
544            let mut file = File::create(full_path)?;
545            file.write_all(content)?;
546            Ok(())
547        };
548        if let Ok(ref key) = self.get_private_key() {
549            write_file("_pkey.pem", key)?;
550        }
551        write_file(self.pem_extension(), &self.get_pem()?)?;
552        Ok(())
553    }
554}
555/// Holds the generated Certificate Signing Request (CSR) and its associated private key.
556pub struct Csr {
557    /// The X.509 certificate signing request.
558    pub csr: X509Req,
559    /// The private key used to generate the CSR.
560    ///
561    /// This is optional to allow flexibility in cases where the key is managed or stored separately.
562    pub pkey: Option<PKey<Private>>,
563}
564
565impl X509Parts for Csr {
566    fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
567        Ok(self.csr.to_pem()?)
568    }
569
570    fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
571        match self.pkey {
572            Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
573            _ => Err("No private key found".into()),
574        }
575    }
576    fn pem_extension(&self) -> &'static str {
577        "_csr.pem"
578    }
579}
580/// Helper trait to document that Csr implements X509Common
581pub trait CsrX509Common: X509Common {}
582impl CsrX509Common for Csr {}
583
584/// Holds configuration options for creating a certificate from a Certificate Signing Request (CSR).
585pub struct CsrOptions {
586    valid_to: Asn1Time,
587    valid_from: Asn1Time,
588    ca: bool,
589    policies: Vec<CertificatePolicy>,
590}
591impl Default for CsrOptions {
592    fn default() -> Self {
593        Self::new()
594    }
595}
596
597impl CsrOptions {
598    /// Creates a default `CsrOptions` instance:
599    /// - `valid_from` is set to today.
600    /// - `valid_to` is set to one year from today.
601    /// - `ca` is set to `false`.
602    pub fn new() -> Self {
603        Self {
604            ca: false,
605            valid_from: Asn1Time::days_from_now(0).unwrap(), // today
606            valid_to: Asn1Time::days_from_now(365).unwrap(), // one year from now
607            policies: Default::default(),
608        }
609    }
610
611    /// Sets the start date from which the certificate should be valid.
612    ///
613    /// # Arguments
614    /// * `valid_from` - A string in the format `yyyy-mm-dd`.
615    pub fn valid_from(mut self, valid_from: &str) -> Self {
616        self.valid_from =
617            create_asn1_time_from_date(valid_from).expect("Failed to parse valid_from date");
618        self
619    }
620
621    /// Sets the end date after which the certificate should no longer be valid.
622    ///
623    /// # Arguments
624    /// * `valid_to` - A string in the format `yyyy-mm-dd`.
625    pub fn valid_to(mut self, valid_to: &str) -> Self {
626        self.valid_to =
627            create_asn1_time_from_date(valid_to).expect("Failed to parse valid_to date");
628        self
629    }
630
631    /// Specifies whether the certificate should be a Certificate Authority (CA).
632    ///
633    /// # Arguments
634    /// * `ca` - `true` if the certificate should be a CA, `false` otherwise.
635    pub fn is_ca(mut self, ca: bool) -> Self {
636        self.ca = ca;
637        self
638    }
639    /// Add optional certificate policies
640    ///
641    /// # Arguments
642    /// * `policies` - A list of certificate policies
643    pub fn certificate_policies(mut self, policies: Vec<CertificatePolicy>) -> Self {
644        self.policies = policies;
645        self
646    }
647}
648impl Csr {
649    /// Read a certificate signing request from file
650    pub fn load_csr<C: AsRef<Path>>(csr_pem_file: C) -> Result<Self, Box<dyn std::error::Error>> {
651        let cert_pem = std::fs::read(csr_pem_file)?;
652        let cs_req = X509Req::from_pem(&cert_pem)?;
653        Ok(Self {
654            csr: cs_req,
655            pkey: None,
656        })
657    }
658    /// Create a signed certificate from a certificate signing request(csr)
659    pub fn build_signed_certificate(
660        &self,
661        signer: &Certificate,
662        options: CsrOptions,
663    ) -> Result<Certificate, Box<dyn std::error::Error>> {
664        let can_sign = can_sign_cert(&signer.x509)?;
665        if !can_sign {
666            let err = format!(
667                "Trying to sign with non CA and/or no key usage that allow signing for signer certificate:{:?}",
668                signer.x509.issuer_name()
669            );
670            return Err(err.into());
671        }
672        let mut builder = X509Builder::new()?;
673        builder.set_version(2)?;
674        builder.set_subject_name(self.csr.subject_name())?;
675        builder.set_issuer_name(signer.x509.subject_name())?;
676        let csr_public_key = self.csr.public_key()?;
677        builder.set_pubkey(&csr_public_key)?;
678
679        let der = self.csr.to_der()?;
680        let parsed_csr = X509CertificationRequest::from_der(&der)?;
681
682        let req_ext = parsed_csr.1.requested_extensions();
683        let mut any_key_used = false;
684        if let Some(exts) = req_ext {
685            for ext in exts {
686                match ext {
687                    ParsedExtension::KeyUsage(ku) => {
688                        any_key_used = true;
689                        let mut cert_sign_added = false;
690                        let mut crl_sign_added = false;
691                        let mut usage = openssl::x509::extension::KeyUsage::new();
692                        if ku.digital_signature() {
693                            usage.digital_signature();
694                        }
695                        if ku.key_encipherment() {
696                            usage.key_encipherment();
697                        }
698                        if ku.key_cert_sign() {
699                            cert_sign_added = true;
700                            usage.key_cert_sign();
701                        }
702                        if ku.non_repudiation() {
703                            usage.non_repudiation();
704                        }
705                        if ku.crl_sign() {
706                            crl_sign_added = true;
707                            usage.crl_sign();
708                        }
709
710                        if options.ca {
711                            if !cert_sign_added {
712                                usage.key_cert_sign();
713                            }
714                            if !crl_sign_added {
715                                usage.crl_sign();
716                            }
717                        }
718                        builder.append_extension(usage.build()?)?;
719                    }
720                    ParsedExtension::ExtendedKeyUsage(eku) => {
721                        let mut ext = openssl::x509::extension::ExtendedKeyUsage::new();
722                        if eku.server_auth {
723                            ext.server_auth();
724                        }
725                        if eku.client_auth {
726                            ext.client_auth();
727                        }
728                        if eku.code_signing {
729                            ext.code_signing();
730                        }
731                        if eku.email_protection {
732                            ext.email_protection();
733                        }
734                        builder.append_extension(ext.build()?)?;
735                    }
736                    ParsedExtension::SubjectAlternativeName(san) => {
737                        let mut openssl_san =
738                            openssl::x509::extension::SubjectAlternativeName::new();
739                        for name in &san.general_names {
740                            if let x509_parser::extensions::GeneralName::DNSName(dns) = name {
741                                openssl_san.dns(dns);
742                            }
743                        }
744                        builder.append_extension(
745                            openssl_san.build(&builder.x509v3_context(None, None))?,
746                        )?;
747                    }
748                    _ => {
749                        println!("Unsupported extension: {:?}", ext);
750                    }
751                }
752            }
753        }
754        if options.ca {
755            builder.append_extension(BasicConstraints::new().ca().critical().build()?)?;
756            if !any_key_used {
757                let key_usage = KeyUsage::new().key_cert_sign().crl_sign().build().unwrap();
758                builder.append_extension(key_usage)?;
759            }
760        } else {
761            builder.append_extension(BasicConstraints::new().build()?)?;
762        }
763        builder.set_not_before(&options.valid_from)?;
764        builder.set_not_after(&options.valid_to)?;
765        let serial_number = {
766            let mut serial = BigNum::new()?;
767            serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
768            serial.to_asn1_integer()?
769        };
770        builder.set_serial_number(&serial_number)?;
771        if signer.x509.subject_key_id().is_some() {
772            let aki = AuthorityKeyIdentifier::new()
773                .keyid(true)
774                .issuer(false)
775                .build(&builder.x509v3_context(Some(&signer.x509), None))?;
776            builder.append_extension(aki)?;
777        }
778        let oid = Asn1Object::from_str("2.5.29.14")?; // OID för Subject Key Identifier (SKI)
779        let pubkey_der = self.csr.public_key().unwrap().public_key_to_der()?;
780        let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
781        let der_encoded = yasna::construct_der(|writer| {
782            writer.write_bytes(ski_hash.as_ref());
783        });
784        let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
785        let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
786        builder.append_extension(ext)?;
787        append_certificate_policies(&mut builder, &options.policies)?;
788        let cert: X509 = if is_digestless_key(signer.pkey.as_ref().unwrap()) {
789            let builder_cert = builder.build();
790            sign_certificate_digestless(&builder_cert, signer.pkey.as_ref().unwrap())
791                .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
792            builder_cert
793        } else {
794            builder.sign(signer.pkey.as_ref().unwrap(), MessageDigest::sha256())?;
795            builder.build()
796        };
797
798        Ok(Certificate {
799            x509: cert,
800            pkey: None,
801        })
802    }
803}
804/// Holds the generated X.509 certificate and its associated private key.
805#[derive(Clone)]
806pub struct Certificate {
807    /// The X.509 certificate.
808    pub x509: X509,
809    /// The private key used to generate or sign the certificate.
810    ///
811    /// This is optional to allow for cases where the key is stored or managed separately.
812    pub pkey: Option<PKey<Private>>,
813}
814
815impl X509Parts for Certificate {
816    fn get_pem(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
817        Ok(self.x509.to_pem()?)
818    }
819
820    fn get_private_key(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
821        match self.pkey {
822            Some(ref pkey) => Ok(pkey.private_key_to_pem_pkcs8()?),
823            _ => Err("No private key found".into()),
824        }
825    }
826    fn pem_extension(&self) -> &'static str {
827        "_cert.pem"
828    }
829}
830
831/// Helper trait to document that Certificate implements X509Common
832pub trait CertificateX509Common: X509Common {}
833impl CertificateX509Common for Certificate {}
834
835impl Certificate {
836    /// Loads a certificate and private key that are in PEM format from file
837    /// and creates an X509 and PKey object.
838    pub fn load_cert_and_key<C: AsRef<Path>, K: AsRef<Path>>(
839        cert_pem_file: C,
840        key_pem_file: K,
841    ) -> Result<Self, Box<dyn std::error::Error>> {
842        let cert_pem = std::fs::read(cert_pem_file)?;
843        let key_pem = std::fs::read(key_pem_file)?;
844        let cert = X509::from_pem(&cert_pem)?;
845        let pkey = PKey::private_key_from_pem(&key_pem)?;
846        Ok(Self {
847            x509: cert,
848            pkey: Some(pkey),
849        })
850    }
851}
852
853/// Defines a common interface for setting X509 certificate or CSR builder fields.
854pub trait BuilderCommon {
855    fn set_common_name(&mut self, name: &str);
856    fn set_signer(&mut self, signer: &str);
857    fn set_country_name(&mut self, name: &str);
858    fn set_state_province(&mut self, name: &str);
859    fn set_organization(&mut self, name: &str);
860    fn set_organization_unit(&mut self, name: &str);
861    fn set_alternative_names(&mut self, alternative_names: Vec<&str>);
862    fn set_locality_time(&mut self, locality_time: &str);
863    fn set_key_type(&mut self, key_type: KeyType);
864    fn set_signature_alg(&mut self, signature_alg: HashAlg);
865    fn set_key_usage(&mut self, key_usage: HashSet<Usage>);
866}
867
868/// Stores common configurable fields used during X509 certificate or CSR generation.
869#[derive(Debug)]
870pub struct BuilderFields {
871    common_name: String,
872    signer: Option<String>, //place holder for maybe future use??
873    alternative_names: HashSet<String>,
874    organization_unit: String,
875    country_name: String,
876    state_province: String,
877    organization: String,
878    locality_time: String,
879    key_type: Option<KeyType>,
880    signature_alg: Option<HashAlg>,
881    usage: Option<HashSet<Usage>>,
882}
883impl BuilderCommon for BuilderFields {
884    // Sets the common name, CN. This value will also be added to alternaitve_names
885    fn set_common_name(&mut self, common_name: &str) {
886        self.common_name = common_name.into();
887        self.alternative_names.insert(String::from(common_name));
888    }
889    // A list of altrnative names(SAN) the Common Name(CN) is always included
890    fn set_alternative_names(&mut self, alternative_names: Vec<&str>) {
891        self.alternative_names
892            .extend(vec_str_to_hs!(alternative_names));
893    }
894    // maybe
895    fn set_signer(&mut self, signer: &str) {
896        self.signer = Some(signer.into());
897    }
898    // Country, a valid two char value
899    fn set_country_name(&mut self, country_name: &str) {
900        self.country_name = country_name.into();
901    }
902    // State, province an utf-8 value
903    fn set_state_province(&mut self, state_province: &str) {
904        self.state_province = state_province.into();
905    }
906    // Org. an utf-8 value
907    fn set_organization(&mut self, organization: &str) {
908        self.organization = organization.into();
909    }
910    // Org. unit an utf-8 value
911    fn set_organization_unit(&mut self, organization_unit: &str) {
912        self.organization_unit = organization_unit.into();
913    }
914    // Locality, represents the city, town, or locality of the certificate subject
915    fn set_locality_time(&mut self, locality_time: &str) {
916        self.locality_time = locality_time.into();
917    }
918    // Selects what type of key to use RSA or elliptic
919    fn set_key_type(&mut self, key_type: KeyType) {
920        self.key_type = Some(key_type);
921    }
922    // Selects what alg to use for signature
923    fn set_signature_alg(&mut self, signature_alg: HashAlg) {
924        self.signature_alg = Some(signature_alg);
925    }
926
927    // Set what the certificate are allowed to do, KeyUsage and ExtendeKeyUsage
928    fn set_key_usage(&mut self, key_usage: HashSet<Usage>) {
929        match &mut self.usage {
930            Some(existing_usage) => {
931                existing_usage.extend(key_usage);
932            }
933            None => {
934                self.usage = Some(key_usage);
935            }
936        };
937    }
938}
939
940impl Default for BuilderFields {
941    /// Returns default values for all fields
942    fn default() -> Self {
943        Self {
944            common_name: Default::default(),
945            signer: Default::default(),
946            alternative_names: Default::default(),
947            country_name: Default::default(),
948            state_province: Default::default(),
949            organization: Default::default(),
950            organization_unit: Default::default(),
951            locality_time: Default::default(),
952            key_type: Default::default(),
953            signature_alg: Default::default(),
954            usage: Default::default(),
955        }
956    }
957}
958/// Provides a builder interface for configuring X509 certificate or CSR fields.
959pub trait UseesBuilderFields: Sized {
960    /// Returns a mutable reference to the internal `BuilderFields` structure.
961    fn fields_mut(&mut self) -> &mut BuilderFields;
962
963    /// Sets the Common Name (CN) of the certificate subject.
964    ///
965    /// This value will also be added to the list of Subject Alternative Names (SAN).
966    fn common_name(mut self, common_name: &str) -> Self {
967        self.fields_mut().set_common_name(common_name);
968        self
969    }
970    /// Sets the signer name or identifier for the certificate.
971    fn signer(mut self, signer: &str) -> Self {
972        self.fields_mut().set_signer(signer);
973        self
974    }
975    /// Sets the list of Subject Alternative Names (SAN).
976    ///
977    /// The Common Name (CN) is always included automatically.
978    fn alternative_names(mut self, alternative_names: Vec<&str>) -> Self {
979        self.fields_mut().set_alternative_names(alternative_names);
980        self
981    }
982    /// Sets the country name (C), which must be a valid two-letter country code.
983    fn country_name(mut self, country_name: &str) -> Self {
984        self.fields_mut().set_country_name(country_name);
985        self
986    }
987    /// Sets the state or province name (ST) as a UTF-8 string.
988    fn state_province(mut self, state_province: &str) -> Self {
989        self.fields_mut().set_state_province(state_province);
990        self
991    }
992    /// Sets the organization name (O) as a UTF-8 string.
993    fn organization(mut self, organization: &str) -> Self {
994        self.fields_mut().set_organization(organization);
995        self
996    }
997    /// Sets the locality name (L), typically representing the city or town.
998    fn locality_time(mut self, locality_time: &str) -> Self {
999        self.fields_mut().set_locality_time(locality_time);
1000        self
1001    }
1002    /// Sets the type of key to generate (e.g., RSA or Elliptic Curve).
1003    fn key_type(mut self, key_type: KeyType) -> Self {
1004        self.fields_mut().set_key_type(key_type);
1005        self
1006    }
1007    /// Sets the signature algorithm to use when signing the certificate.
1008    fn signature_alg(mut self, signature_alg: HashAlg) -> Self {
1009        self.fields_mut().set_signature_alg(signature_alg);
1010        self
1011    }
1012
1013    /// Sets the allowed usages for the certificate (e.g., key signing, digital signature).
1014    ///
1015    /// This includes both `KeyUsage` and `ExtendedKeyUsage` extensions.
1016    fn key_usage(mut self, key_usage: HashSet<Usage>) -> Self {
1017        self.fields_mut().set_key_usage(key_usage);
1018        self
1019    }
1020}
1021
1022/// Builder for creating a new certificate and private key
1023pub struct CertBuilder {
1024    fields: BuilderFields,
1025    valid_from: Asn1Time,
1026    valid_to: Asn1Time,
1027    policies: Vec<CertificatePolicy>,
1028    ca: bool,
1029}
1030
1031impl UseesBuilderFields for CertBuilder {
1032    fn fields_mut(&mut self) -> &mut BuilderFields {
1033        &mut self.fields
1034    }
1035}
1036impl Default for CertBuilder {
1037    fn default() -> Self {
1038        Self::new()
1039    }
1040}
1041
1042impl CertBuilder {
1043    /// Create a new CertBuilder with defaults and one year from now as valid date
1044    pub fn new() -> Self {
1045        Self {
1046            fields: BuilderFields::default(),
1047            valid_from: Asn1Time::days_from_now(0).unwrap(), // today
1048            valid_to: Asn1Time::days_from_now(365).unwrap(), // one year from now
1049            ca: false,
1050            policies: Default::default(),
1051        }
1052    }
1053    /// Add optional certificate policies
1054    ///
1055    /// # Arguments
1056    /// * `policies` - A list of certificate policies
1057    pub fn certificate_policies(mut self, policies: Vec<CertificatePolicy>) -> Self {
1058        self.policies = policies;
1059        self
1060    }
1061    /// Sets the start date from which the certificate should be valid.
1062    ///
1063    /// # Arguments
1064    /// * `valid_from` - A string in the format `yyyy-mm-dd`.
1065    pub fn valid_from(mut self, valid_from: &str) -> Self {
1066        self.valid_from =
1067            create_asn1_time_from_date(valid_from).expect("Failed to parse valid_from date");
1068        self
1069    }
1070    /// Sets the end date after which the certificate should no longer be valid.
1071    ///
1072    /// # Arguments
1073    /// * `valid_to` - A string in the format `yyyy-mm-dd`.
1074    pub fn valid_to(mut self, valid_to: &str) -> Self {
1075        self.valid_to =
1076            create_asn1_time_from_date(valid_to).expect("Failed to parse valid_to date");
1077        self
1078    }
1079    /// Specifies whether the certificate should be a Certificate Authority (CA).
1080    ///
1081    /// # Arguments
1082    /// * `ca` - `true` if the certificate should be a CA, `false` otherwise.
1083    pub fn is_ca(mut self, ca: bool) -> Self {
1084        if ca {
1085            self.ca = ca;
1086            self.fields
1087                .set_key_usage(HashSet::from([Usage::certsign, Usage::crlsign]));
1088        }
1089        self
1090    }
1091
1092    /// create a self signed x509 certificate and private key
1093    pub fn build_and_self_sign(&self) -> Result<Certificate, Box<dyn std::error::Error>> {
1094        let (mut builder, pkey) = self.prepare_x509_builder(None)?;
1095
1096        // ML-KEM keys cannot produce signatures, so a self-signed certificate is
1097        // impossible — issue via build_and_sign() with a separate signing CA.
1098        #[cfg(feature = "pqc")]
1099        if is_mlkem_pkey(&pkey) {
1100            return Err("ML-KEM (FIPS 203) is a key-encapsulation key and cannot \
1101                produce signatures, so it cannot self-sign a certificate. Issue an \
1102                ML-KEM certificate with CertBuilder::build_and_sign() using a signing \
1103                CA (e.g. an ML-DSA or ECDSA CA) instead."
1104                .into());
1105        }
1106
1107        let ca_cert: X509 = if is_digestless_key(&pkey) {
1108            let build_cert = builder.build();
1109            sign_certificate_digestless(&build_cert, &pkey)
1110                .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
1111            build_cert
1112        } else {
1113            builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
1114            builder.build()
1115        };
1116
1117        Ok(Certificate {
1118            x509: ca_cert,
1119            pkey: Some(pkey),
1120        })
1121    }
1122    /// Create a signed certificate and private key
1123    pub fn build_and_sign(
1124        &self,
1125        signer: &Certificate,
1126    ) -> Result<Certificate, Box<dyn std::error::Error>> {
1127        let can_sign = can_sign_cert(&signer.x509)?;
1128        if !can_sign {
1129            let err = format!(
1130                "Trying to sign with non CA and/or no key usage that allow signing for signer certificate:{:?}",
1131                signer.x509.issuer_name()
1132            );
1133            return Err(err.into());
1134        }
1135        let (mut builder, pkey) = self.prepare_x509_builder(Some(signer))?;
1136        let signer_key = signer.pkey.as_ref().unwrap();
1137        let cert: X509 = if is_digestless_key(signer_key) {
1138            let build_cert = builder.build();
1139            sign_certificate_digestless(&build_cert, signer_key)
1140                .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
1141            build_cert
1142        } else {
1143            builder.sign(signer_key, select_hash(&self.fields.signature_alg))?;
1144            builder.build()
1145        };
1146        Ok(Certificate {
1147            x509: cert,
1148            pkey: Some(pkey),
1149        })
1150    }
1151
1152    fn prepare_x509_builder(
1153        &self,
1154        signer: Option<&Certificate>,
1155    ) -> Result<(X509Builder, PKey<Private>), Box<dyn std::error::Error>> {
1156        let mut name_builder = X509NameBuilder::new()?;
1157        name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
1158        if !self.fields.country_name.trim().is_empty() {
1159            name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
1160        }
1161        if !self.fields.state_province.trim().is_empty() {
1162            name_builder
1163                .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
1164        }
1165        if !self.fields.locality_time.trim().is_empty() {
1166            name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
1167        }
1168        if !self.fields.organization.trim().is_empty() {
1169            name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
1170        }
1171        if !self.fields.organization_unit.trim().is_empty() {
1172            name_builder
1173                .append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, &self.fields.organization_unit)?;
1174        }
1175
1176        let name = name_builder.build();
1177
1178        let mut builder = X509::builder()?;
1179        builder.set_version(2)?;
1180
1181        let serial_number = {
1182            let mut serial = BigNum::new()?;
1183            serial.rand(159, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
1184            serial.to_asn1_integer()?
1185        };
1186
1187        let pkey = select_key(&self.fields.key_type).unwrap();
1188        builder.set_serial_number(&serial_number)?;
1189        builder.set_subject_name(&name)?;
1190        builder.set_pubkey(&pkey)?;
1191        builder.set_not_before(&self.valid_from)?;
1192        builder.set_not_after(&self.valid_to)?;
1193        match signer {
1194            Some(signer) => builder.set_issuer_name(signer.x509.subject_name())?,
1195            None => builder.set_issuer_name(&name)?,
1196        }
1197
1198        let key_usage = self.fields.usage.clone().unwrap_or_default();
1199        append_certificate_policies(&mut builder, &self.policies)?;
1200        // Post-quantum signature keys (ML-DSA / SLH-DSA) are signature-only and
1201        // cannot perform key encipherment; reject the contradictory combination.
1202        #[cfg(feature = "pqc")]
1203        {
1204            if is_pqc_pkey(&pkey) && key_usage.contains(&Usage::encipherment) {
1205                return Err("keyEncipherment (Usage::encipherment) is not valid for a \
1206                    post-quantum signature key (ML-DSA / SLH-DSA): these algorithms are \
1207                    signature-only and cannot perform key encipherment. Use Usage::signature \
1208                    (and certsign/crlsign for a CA) instead."
1209                    .into());
1210            }
1211        }
1212
1213        // ML-KEM (FIPS 203) is a key-encapsulation key. Per
1214        // draft-ietf-lamps-kyber-certificates, when an ML-KEM key appears in the
1215        // SPKI and KeyUsage is present it MUST assert keyEncipherment and nothing
1216        // else (no digitalSignature, keyAgreement, dataEncipherment, etc.).
1217        #[cfg(feature = "pqc")]
1218        {
1219            if is_mlkem_pkey(&pkey)
1220                && !key_usage.is_empty()
1221                && key_usage != HashSet::from([Usage::encipherment])
1222            {
1223                return Err("an ML-KEM (FIPS 203) key may only assert keyEncipherment \
1224                    (Usage::encipherment) in its KeyUsage — no other bit is permitted \
1225                    (no digitalSignature, keyAgreement, dataEncipherment, certsign, or \
1226                    crlsign). Set key_usage to exactly {Usage::encipherment}, or omit it."
1227                    .into());
1228            }
1229        }
1230
1231        if self.ca {
1232            builder.append_extension(BasicConstraints::new().ca().critical().build()?)?;
1233        } else {
1234            builder.append_extension(BasicConstraints::new().build()?)?;
1235        }
1236
1237        let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
1238        if tracked_key_usage.is_used() {
1239            builder.append_extension(tracked_key_usage.into_inner().build()?)?;
1240        }
1241        if tracked_extended_key_usage.is_used() {
1242            builder.append_extension(tracked_extended_key_usage.into_inner().build()?)?;
1243        }
1244
1245        let mut san = SubjectAlternativeName::new();
1246        for s in &self.fields.alternative_names {
1247            san.dns(s);
1248        }
1249        if let Some(signer_cert) = signer {
1250            builder.append_extension(
1251                san.build(&builder.x509v3_context(Some(&signer_cert.x509), None))?,
1252            )?;
1253            if signer_cert.x509.subject_key_id().is_some() {
1254                let aki = AuthorityKeyIdentifier::new()
1255                    .keyid(true)
1256                    .issuer(false)
1257                    .build(&builder.x509v3_context(Some(&signer_cert.x509), None))?;
1258                builder.append_extension(aki)?;
1259            }
1260        } else {
1261            // add aki that is the same as ski for self signed
1262            builder.append_extension(san.build(&builder.x509v3_context(None, None))?)?;
1263            let oid = Asn1Object::from_str("2.5.29.35")?; // OID för Authority Key Identifier (AKI)
1264            let pubkey_der = pkey.public_key_to_der()?;
1265            let aki_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
1266            let der_encoded = yasna::construct_der(|writer| {
1267                writer.write_sequence(|writer| {
1268                    writer
1269                        .next()
1270                        .write_tagged_implicit(yasna::Tag::context(0), |writer| {
1271                            writer.write_bytes(aki_hash.as_ref());
1272                        })
1273                })
1274            });
1275            let aki_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
1276            let ext = X509Extension::new_from_der(oid.as_ref(), false, &aki_asn1)?;
1277            builder.append_extension(ext)?;
1278        }
1279        // tried
1280        // let ski = SubjectKeyIdentifier::new().build(&builder.x509v3_context(None, None))?;
1281        // but got miss/match in hash values so I calculate the ski explicitly with sha1
1282        // to verify with openssl cli
1283        // RSA:
1284        // openssl x509 -in mytestca_cert.pem -inform PEM -pubkey -noout | openssl rsa -pubin -outform DER | openssl dgst -c -sha1
1285        // EC:
1286        // openssl x509 -in mytestca_cert.pem -inform PEM -pubkey -noout| openssl pkey -pubin -outform DER| openssl dgst -c -sha1
1287        let oid = Asn1Object::from_str("2.5.29.14")?; // OID för Subject Key Identifier (SKI)
1288        let pubkey_der = pkey.public_key_to_der()?;
1289        let ski_hash = hash(MessageDigest::sha1(), &pubkey_der)?;
1290        let der_encoded = yasna::construct_der(|writer| {
1291            writer.write_bytes(ski_hash.as_ref());
1292        });
1293        let ski_asn1 = Asn1OctetString::new_from_bytes(&der_encoded)?;
1294        let ext = X509Extension::new_from_der(oid.as_ref(), false, &ski_asn1)?;
1295        builder.append_extension(ext)?;
1296
1297        Ok((builder, pkey))
1298    }
1299}
1300
1301/// Builder for creating a new certificate signing request and private key
1302pub struct CsrBuilder {
1303    fields: BuilderFields,
1304}
1305impl UseesBuilderFields for CsrBuilder {
1306    fn fields_mut(&mut self) -> &mut BuilderFields {
1307        &mut self.fields
1308    }
1309}
1310impl Default for CsrBuilder {
1311    fn default() -> Self {
1312        Self::new()
1313    }
1314}
1315
1316impl CsrBuilder {
1317    /// Create a new CsrBuilder with defaults
1318    pub fn new() -> Self {
1319        Self {
1320            fields: BuilderFields::default(),
1321        }
1322    }
1323
1324    /// Builds and returns a Certificate Signing Request (CSR) based on the configured builder fields.
1325    ///
1326    /// This function constructs the subject name, sets the public key, and adds relevant X.509 extensions
1327    /// such as Key Usage, Extended Key Usage, and Subject Alternative Names (SAN).
1328    /// It supports signing with both traditional algorithms and Ed25519.
1329    ///
1330    /// # Returns
1331    /// - `Ok(Csr)` if the CSR was successfully built and signed.
1332    /// - `Err(Box<dyn std::error::Error>)` if any step in the CSR creation process fails.
1333    ///
1334    /// # Errors
1335    /// This function may return errors in the following cases:
1336    /// - Failure to initialize or build the X509 name or request.
1337    /// - Failure to select or use the appropriate key type.
1338    /// - Failure to build or add X.509 extensions.
1339    /// - Failure to sign the CSR, especially with Ed25519.
1340    ///
1341    /// # Extensions Added
1342    /// - **Key Usage** and **Extended Key Usage**: Based on the builder's `usage` field.
1343    /// - **Subject Alternative Names (SAN)**: Includes all entries from `alternative_names`.
1344    ///
1345    /// # Signing Behavior
1346    /// - If the key type is Ed25519, uses a custom signing function.
1347    /// - Otherwise, signs using the selected hash algorithm.
1348    ///
1349    /// # Example
1350    /// ```rust
1351    /// use cert_helper::certificate::CsrBuilder;
1352    /// use crate::cert_helper::certificate::UseesBuilderFields;
1353    /// let builder = CsrBuilder::new().common_name("example.com");
1354    /// let csr = builder.certificate_signing_request().unwrap();
1355    /// ```
1356    pub fn certificate_signing_request(self) -> Result<Csr, Box<dyn std::error::Error>> {
1357        let mut name_builder = X509NameBuilder::new()?;
1358        name_builder.append_entry_by_nid(Nid::COMMONNAME, &self.fields.common_name)?;
1359        if !self.fields.country_name.trim().is_empty() {
1360            name_builder.append_entry_by_nid(Nid::COUNTRYNAME, &self.fields.country_name)?;
1361        }
1362        if !self.fields.state_province.trim().is_empty() {
1363            name_builder
1364                .append_entry_by_nid(Nid::STATEORPROVINCENAME, &self.fields.state_province)?;
1365        }
1366        if !self.fields.locality_time.trim().is_empty() {
1367            name_builder.append_entry_by_nid(Nid::LOCALITYNAME, &self.fields.locality_time)?;
1368        }
1369        if !self.fields.organization.trim().is_empty() {
1370            name_builder.append_entry_by_nid(Nid::ORGANIZATIONNAME, &self.fields.organization)?;
1371        }
1372        let name = name_builder.build();
1373        let mut builder = X509ReqBuilder::new()?;
1374        builder.set_version(0)?;
1375        builder.set_subject_name(&name)?;
1376        let pkey = select_key(&self.fields.key_type).unwrap();
1377        builder.set_pubkey(&pkey)?;
1378        let key_usage = self.fields.usage.clone().unwrap_or_default();
1379
1380        // Post-quantum signature keys (ML-DSA / SLH-DSA) cannot perform key
1381        // encipherment; reject the contradictory combination in CSRs too.
1382        #[cfg(feature = "pqc")]
1383        {
1384            if is_pqc_pkey(&pkey) && key_usage.contains(&Usage::encipherment) {
1385                return Err("keyEncipherment (Usage::encipherment) is not valid for a \
1386                    post-quantum signature key (ML-DSA / SLH-DSA): these algorithms are \
1387                    signature-only and cannot perform key encipherment. Use Usage::signature \
1388                    instead."
1389                    .into());
1390            }
1391        }
1392
1393        // ML-KEM keyEncipherment-only lint (see prepare_x509_builder for the
1394        // rationale). Checked before the can't-sign guard below so a contradictory
1395        // KeyUsage is reported precisely.
1396        #[cfg(feature = "pqc")]
1397        {
1398            if is_mlkem_pkey(&pkey)
1399                && !key_usage.is_empty()
1400                && key_usage != HashSet::from([Usage::encipherment])
1401            {
1402                return Err("an ML-KEM (FIPS 203) key may only assert keyEncipherment \
1403                    (Usage::encipherment) in its KeyUsage — no other bit is permitted \
1404                    (no digitalSignature, keyAgreement, dataEncipherment, certsign, or \
1405                    crlsign). Set key_usage to exactly {Usage::encipherment}, or omit it."
1406                    .into());
1407            }
1408        }
1409
1410        // ML-KEM cannot sign, and a PKCS#10 CSR requires a self-signature for
1411        // proof-of-possession — so an ML-KEM CSR cannot be produced here.
1412        #[cfg(feature = "pqc")]
1413        if is_mlkem_pkey(&pkey) {
1414            return Err(
1415                "ML-KEM (FIPS 203) is a key-encapsulation key and cannot sign \
1416                a CSR (PKCS#10 requires a self-signature for proof-of-possession). \
1417                Issue the ML-KEM certificate directly with CertBuilder::build_and_sign() \
1418                using a signing CA instead."
1419                    .into(),
1420            );
1421        }
1422
1423        let mut extensions = Stack::new()?;
1424
1425        let (tracked_key_usage, tracked_extended_key_usage) = get_key_usage(&Some(key_usage));
1426        if tracked_key_usage.is_used() {
1427            extensions.push(tracked_key_usage.inner.build()?)?;
1428        }
1429        if tracked_extended_key_usage.is_used() {
1430            extensions.push(tracked_extended_key_usage.inner.build()?)?;
1431        }
1432
1433        let mut san = SubjectAlternativeName::new();
1434        for s in &self.fields.alternative_names {
1435            san.dns(s);
1436        }
1437        extensions.push(san.build(&builder.x509v3_context(None))?)?;
1438
1439        builder.add_extensions(&extensions)?;
1440        let csr: X509Req = if is_digestless_key(&pkey) {
1441            let builder_csr = builder.build();
1442            sign_x509_req_digestless(&builder_csr, &pkey)
1443                .map_err(|e| format!("Failed to sign certificate with digestless key: {}", e))?;
1444            builder_csr
1445        } else {
1446            builder.sign(&pkey, select_hash(&self.fields.signature_alg))?;
1447            builder.build()
1448        };
1449        Ok(Csr {
1450            csr,
1451            pkey: Some(pkey),
1452        })
1453    }
1454}
1455struct TrackedExtendedKeyUsage {
1456    inner: ExtendedKeyUsage,
1457    used: bool,
1458}
1459
1460impl TrackedExtendedKeyUsage {
1461    fn new() -> Self {
1462        Self {
1463            inner: ExtendedKeyUsage::new(),
1464            used: false,
1465        }
1466    }
1467
1468    fn client_auth(&mut self) {
1469        self.inner.client_auth();
1470        self.used = true;
1471    }
1472
1473    fn server_auth(&mut self) {
1474        self.inner.server_auth();
1475        self.used = true;
1476    }
1477
1478    fn is_used(&self) -> bool {
1479        self.used
1480    }
1481
1482    fn into_inner(self) -> ExtendedKeyUsage {
1483        self.inner
1484    }
1485}
1486
1487struct TrackedKeyUsage {
1488    inner: KeyUsage,
1489    used: bool,
1490}
1491
1492impl TrackedKeyUsage {
1493    fn new() -> Self {
1494        Self {
1495            inner: KeyUsage::new(),
1496            used: false,
1497        }
1498    }
1499
1500    fn digital_signature(&mut self) {
1501        self.inner.digital_signature();
1502        self.used = true;
1503    }
1504
1505    fn non_repudiation(&mut self) {
1506        self.inner.non_repudiation();
1507        self.used = true;
1508    }
1509
1510    fn key_encipherment(&mut self) {
1511        self.inner.key_encipherment();
1512        self.used = true;
1513    }
1514
1515    fn key_cert_sign(&mut self) {
1516        self.inner.key_cert_sign();
1517        self.used = true;
1518    }
1519
1520    fn crl_sign(&mut self) {
1521        self.inner.crl_sign();
1522        self.used = true;
1523    }
1524
1525    fn is_used(&self) -> bool {
1526        self.used
1527    }
1528
1529    fn into_inner(self) -> KeyUsage {
1530        self.inner
1531    }
1532}
1533/// Parse a dotted OID string into a yasna ObjectIdentifier.
1534fn parse_oid(dotted: &str) -> Result<ObjectIdentifier, Box<dyn std::error::Error>> {
1535    let components = dotted
1536        .split('.')
1537        .map(|c| c.parse::<u64>())
1538        .collect::<Result<Vec<u64>, _>>()
1539        .map_err(|_| format!("invalid policy OID: {dotted}"))?;
1540    Ok(ObjectIdentifier::new(components))
1541}
1542
1543fn append_certificate_policies(
1544    builder: &mut X509Builder,
1545    policies: &[CertificatePolicy],
1546) -> Result<(), Box<dyn std::error::Error>> {
1547    if policies.is_empty() {
1548        return Ok(());
1549    }
1550    let oids = policies
1551        .iter()
1552        .map(|p| parse_oid(p.oid()))
1553        .collect::<Result<Vec<_>, _>>()?;
1554    let der = yasna::construct_der(|w| {
1555        w.write_sequence(|seq| {
1556            for oid in &oids {
1557                seq.next().write_sequence(|pi| pi.next().write_oid(oid));
1558            }
1559        });
1560    });
1561    let oid = Asn1Object::from_str("2.5.29.32")?;
1562    let value = Asn1OctetString::new_from_bytes(&der)?;
1563    builder.append_extension(X509Extension::new_from_der(oid.as_ref(), false, &value)?)?;
1564    Ok(())
1565}
1566
1567/// Verifies a certificate against a root certificate and the intermediate
1568/// chain leading up to it.
1569/// Note: The root certificate should not be included in the chain.
1570pub fn verify_cert(
1571    cert: &X509,
1572    ca: &X509,
1573    cert_chain: Vec<&X509>,
1574) -> Result<bool, Box<dyn std::error::Error>> {
1575    // Build a certificate store and add the issuer
1576    let mut store_builder = X509StoreBuilder::new()?;
1577    store_builder.add_cert(ca.clone())?;
1578    let store = store_builder.build();
1579
1580    // Create a verification context
1581    let mut ctx = X509StoreContext::new()?;
1582    let mut chain = Stack::new()?; // create an empty chain
1583    cert_chain
1584        .iter()
1585        .try_for_each(|c| chain.push((*c).clone()))?;
1586    ctx.init(&store, cert, &chain, |c| c.verify_cert())?;
1587    let verified = ctx.error() == openssl::x509::X509VerifyResult::OK;
1588    Ok(verified)
1589}
1590
1591/// Takes a vector of certificates and returns a vector ordered
1592/// from the root to the leaf, with the leaf certificate as the last element.
1593///
1594/// For example, if the input list contains `ca2`, `leaf`, and `ca1`,
1595/// and the signing order is `ca1 -> ca2 -> leaf`,
1596/// the returned vector will be `[ca1, ca2, leaf]`.
1597///
1598/// If multiple valid chains are possible, the longest one is returned.
1599pub fn create_cert_chain_from_cert_list(
1600    certs: Vec<X509>,
1601) -> Result<Vec<X509>, Box<dyn std::error::Error>> {
1602    let mut subject_map: HashMap<Vec<u8>, X509> = HashMap::new();
1603    let mut issuer_map: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
1604
1605    for cert in &certs {
1606        let subject = cert.subject_name().to_der()?;
1607        let issuer = cert.issuer_name().to_der()?;
1608        subject_map.insert(subject.clone(), cert.clone());
1609        issuer_map.insert(subject, issuer);
1610    }
1611
1612    // Find leaf certificates (those that are not issuers of any other cert)
1613    let all_issuers: Vec<Vec<u8>> = issuer_map.values().cloned().collect();
1614    let leaf_certs: Vec<X509> = subject_map
1615        .iter()
1616        .filter(|(subject, _)| !all_issuers.contains(subject))
1617        .map(|(_, cert)| cert.clone())
1618        .collect();
1619
1620    // Try to build the longest chain from each leaf
1621    let mut longest_chain = Vec::new();
1622
1623    for leaf in leaf_certs {
1624        let mut chain = vec![leaf.clone()];
1625        let mut current_cert = leaf;
1626
1627        while let Ok(issuer_der) = current_cert.issuer_name().to_der() {
1628            if let Some(parent_cert) = subject_map.get(&issuer_der) {
1629                if parent_cert.subject_name().to_der()? == current_cert.subject_name().to_der()? {
1630                    break; // Self-signed, stop here
1631                }
1632                chain.push(parent_cert.clone());
1633                current_cert = parent_cert.clone();
1634            } else {
1635                break; // Issuer not found in the list
1636            }
1637        }
1638
1639        if chain.len() > longest_chain.len() {
1640            longest_chain = chain;
1641        }
1642    }
1643
1644    // Reverse to have root (or highest known CA) first
1645    longest_chain.reverse();
1646    Ok(longest_chain)
1647}
1648
1649fn create_asn1_time_from_date(date_str: &str) -> Result<Asn1Time, Box<dyn std::error::Error>> {
1650    let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d")?;
1651    let datetime = NaiveDateTime::new(date, chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap());
1652    let utc_datetime = Utc.from_utc_datetime(&datetime);
1653    let asn1_time_str = utc_datetime.format("%Y%m%d%H%M%SZ").to_string();
1654    let asn1_time = Asn1Time::from_str(&asn1_time_str)?;
1655    Ok(asn1_time)
1656}
1657
1658fn select_key(key_type: &Option<KeyType>) -> Result<PKey<Private>, ErrorStack> {
1659    match key_type {
1660        Some(KeyType::P224) => {
1661            let group = EcGroup::from_curve_name(Nid::SECP224R1)?;
1662            let ec_key = EcKey::generate(&group)?;
1663            PKey::from_ec_key(ec_key)
1664        }
1665        Some(KeyType::P256) => {
1666            let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
1667            let ec_key = EcKey::generate(&group)?;
1668            PKey::from_ec_key(ec_key)
1669        }
1670        Some(KeyType::P384) => {
1671            let group = EcGroup::from_curve_name(Nid::SECP384R1)?;
1672            let ec_key = EcKey::generate(&group)?;
1673            PKey::from_ec_key(ec_key)
1674        }
1675        Some(KeyType::P521) => {
1676            let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
1677            let ec_key = EcKey::generate(&group)?;
1678            PKey::from_ec_key(ec_key)
1679        }
1680        Some(KeyType::Ed25519) => PKey::generate_ed25519(),
1681        #[cfg(feature = "pqc")]
1682        Some(KeyType::MlDsa44) => generate_pqc_key("ML-DSA-44"),
1683        #[cfg(feature = "pqc")]
1684        Some(KeyType::MlDsa65) => generate_pqc_key("ML-DSA-65"),
1685        #[cfg(feature = "pqc")]
1686        Some(KeyType::MlDsa87) => generate_pqc_key("ML-DSA-87"),
1687        #[cfg(feature = "pqc")]
1688        Some(KeyType::SlhDsaSha2_128s) => generate_pqc_key("SLH-DSA-SHA2-128s"),
1689        #[cfg(feature = "pqc")]
1690        Some(KeyType::SlhDsaSha2_192s) => generate_pqc_key("SLH-DSA-SHA2-192s"),
1691        #[cfg(feature = "pqc")]
1692        Some(KeyType::SlhDsaSha2_256s) => generate_pqc_key("SLH-DSA-SHA2-256s"),
1693        #[cfg(feature = "pqc")]
1694        Some(KeyType::MlKem512) => generate_pqc_key("ML-KEM-512"),
1695        #[cfg(feature = "pqc")]
1696        Some(KeyType::MlKem768) => generate_pqc_key("ML-KEM-768"),
1697        #[cfg(feature = "pqc")]
1698        Some(KeyType::MlKem1024) => generate_pqc_key("ML-KEM-1024"),
1699        Some(KeyType::RSA4096) => {
1700            let rsa = Rsa::generate(4096)?;
1701            PKey::from_rsa(rsa)
1702        }
1703        _ => {
1704            let rsa = Rsa::generate(2048)?;
1705            PKey::from_rsa(rsa)
1706        }
1707    }
1708}
1709
1710fn select_hash(hash_type: &Option<HashAlg>) -> MessageDigest {
1711    match hash_type {
1712        Some(HashAlg::SHA1) => MessageDigest::sha1(),
1713        Some(HashAlg::SHA384) => MessageDigest::sha384(),
1714        Some(HashAlg::SHA512) => MessageDigest::sha512(),
1715        _ => MessageDigest::sha256(),
1716    }
1717}
1718
1719fn get_key_usage(usage: &Option<HashSet<Usage>>) -> (TrackedKeyUsage, TrackedExtendedKeyUsage) {
1720    let mut ku = TrackedKeyUsage::new();
1721    let mut eku = TrackedExtendedKeyUsage::new();
1722    if let Some(usages) = usage {
1723        for u in usages {
1724            match u {
1725                Usage::contentcommitment => {
1726                    ku.non_repudiation();
1727                }
1728                Usage::encipherment => {
1729                    ku.key_encipherment();
1730                }
1731                Usage::certsign => {
1732                    ku.key_cert_sign();
1733                }
1734                Usage::clientauth => {
1735                    eku.client_auth();
1736                }
1737                Usage::signature => {
1738                    ku.digital_signature();
1739                }
1740                Usage::crlsign => {
1741                    ku.crl_sign();
1742                }
1743                Usage::serverauth => {
1744                    eku.server_auth();
1745                }
1746            }
1747        }
1748    }
1749
1750    (ku, eku)
1751}
1752
1753fn can_sign_cert(cert: &X509) -> Result<bool, Box<dyn std::error::Error>> {
1754    let der = cert.to_der()?;
1755    let (_, parsed_cert) = parse_x509_certificate(&der)?;
1756
1757    let mut is_ca = false;
1758    let mut can_sign = false;
1759
1760    // Compare the validity window using OpenSSL's native ASN.1 time comparison
1761    // instead of a panic-prone string round-trip through chrono: `not_before`
1762    // must be at or before now, and now must be strictly before `not_after`.
1763    let now = Asn1Time::days_from_now(0)?;
1764    let valid_time = cert.not_before().compare(&now)? != std::cmp::Ordering::Greater
1765        && cert.not_after().compare(&now)? == std::cmp::Ordering::Greater;
1766
1767    for ext in parsed_cert.tbs_certificate.extensions().iter() {
1768        match &ext.parsed_extension() {
1769            ParsedExtension::BasicConstraints(bc) => {
1770                is_ca = bc.ca;
1771            }
1772            ParsedExtension::KeyUsage(ku) => {
1773                can_sign = ku.key_cert_sign();
1774            }
1775            _ => {}
1776        }
1777    }
1778    Ok(is_ca && can_sign && valid_time)
1779}
1780
1781#[cfg(test)]
1782mod tests {
1783    use super::*;
1784    use std::io::Write;
1785    use std::path::Path;
1786    use tempfile::NamedTempFile;
1787
1788    #[test]
1789    fn save_certificate() {
1790        let ca = CertBuilder::new().common_name("My Test Ca").is_ca(true);
1791        match ca.build_and_self_sign() {
1792            Ok(cert) => {
1793                let output_file = NamedTempFile::new().unwrap();
1794                let full_path = output_file.path();
1795                let parent_dir: &Path = full_path.parent().unwrap();
1796                let file_name: &str = full_path.file_name().unwrap().to_str().unwrap();
1797                cert.save(parent_dir, file_name)
1798                    .expect("Failed to save certificate and key");
1799                let written_file_path = parent_dir.join(file_name);
1800                assert!(written_file_path.exists(), "File was not created");
1801            }
1802            Err(_) => panic!("Failed to creat certificate"),
1803        }
1804    }
1805
1806    #[test]
1807    fn read_certificate_and_key_from_file() {
1808        let cert_pem = b"-----BEGIN CERTIFICATE-----
1809MIICiDCCAemgAwIBAgIUO3+y1WZPRRNs8dmZZTUHMj6TdiowCgYIKoZIzj0EAwQw
1810WzETMBEGA1UEAwwKTXkgVGVzdCBDYTELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0
1811b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMQ8wDQYDVQQKDAZteSBvcmcwHhcN
1812MjUwNzA4MTExMzI2WhcNMjYwNzA4MTExMzI2WjBbMRMwEQYDVQQDDApNeSBUZXN0
1813IENhMQswCQYDVQQGEwJTRTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlT
1814dG9ja2hvbG0xDzANBgNVBAoMBm15IG9yZzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOB
1815hgAEADZXcQK2ihgVTJeGx5FKm1x+R+ivygIvMnkv03faq1LpLU3doKX38DEO/cSW
1816Ev5u+kcjspXeeDPhqJFC8rRAz4awAMk+D0mXEms7xpFPh0HmI6NNcJc5eJ/8ZsEJ
1817GH1a34y0Yn6259gqlwAh2Eh9Nx1579BAanRr8lr+n1tZ09T/9AQho0gwRjAMBgNV
1818HRMEBTADAQH/MAsGA1UdDwQEAwIBBjApBgNVHREEIjAgggZjYS5jb22CCnd3dy5j
1819YS5jb22CCk15IFRlc3QgQ2EwCgYIKoZIzj0EAwQDgYwAMIGIAkIB8NVUgRIuNXmJ
1820cLCQ74Ub7Dqo71S0+iCrZF1YyJA8/q65aqMCT54k5Yx7HRBUUVHbCEpDXRqGPsIH
1821frfe5OmS3qICQgDBn07o0CcyfoSEd+Xoj2+/RBuU0vo9lUP7TKj7tssBxzEQFoxX
1822eE1qT98UIe78FZ+zqjwZTN9MCSsatuim6pXvOA==
1823-----END CERTIFICATE-----";
1824
1825        let key_pem = b"-----BEGIN PRIVATE KEY-----
1826MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAgvZTeQgGysadAX0r
1827aZB5Lk4vjHy5iVuKdvcGdYt9NBvYx+Ib3Uk7vqMag7M1jyHL0Xf9uNtT2mxBmzBG
18283CF+EgOhgYkDgYYABAA2V3ECtooYFUyXhseRSptcfkfor8oCLzJ5L9N32qtS6S1N
18293aCl9/AxDv3ElhL+bvpHI7KV3ngz4aiRQvK0QM+GsADJPg9JlxJrO8aRT4dB5iOj
1830TXCXOXif/GbBCRh9Wt+MtGJ+tufYKpcAIdhIfTcdee/QQGp0a/Ja/p9bWdPU//QE
1831IQ==
1832-----END PRIVATE KEY-----";
1833
1834        let mut cert_file = NamedTempFile::new().expect("Failed to create temp cert file");
1835        let mut key_file = NamedTempFile::new().expect("Failed to create temp key file");
1836
1837        cert_file.write_all(cert_pem).expect("Failed to write cert");
1838        key_file.write_all(key_pem).expect("Failed to write key");
1839
1840        let result = Certificate::load_cert_and_key(cert_file.path(), key_file.path());
1841        assert!(
1842            result.is_ok(),
1843            "Failed to load cert and key: {:?}",
1844            result.err()
1845        );
1846    }
1847
1848    #[test]
1849    fn test_reading_csr_from_file() {
1850        let csr_data = b"-----BEGIN CERTIFICATE REQUEST-----
1851MIICzDCCAbQCAQAwXTEVMBMGA1UEAwwMZXhhbXBsZTIuY29tMQswCQYDVQQGEwJT
1852RTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xDzANBgNV
1853BAoMBk15IG9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeAXpCG
1854hbIayESfdTzOO0DxIMsOAu4kUm0zF0W/+xDUHl6bGy3wlB9S9nzBG/qwqFZ27Om3
1855o4zrZ8K8DBx0ERWNuhMmr0Nx8QpAWBEyxOc08Gn4c3XVBBkRZSn4AIqr9DGtcUqW
1856tQZXvMGF6sRRljiEvOxO6zMzZKTGYwzIeQvH85cQ3uXsw0Kknsw/fcuywaAC8SS9
1857aqs4jiEIgzdhxdH2OVXBNGj4cjVhK309JiWFHS9XJLNV/PKC+F1nkaANQwbW5A4F
18589vya4js9gk8f4SfF1u+qOJEvsDvAb+1xdjXPRzf77eGh3rC4KgGWQ6WrWfW8PItF
1859BDg/jskq3bJXNL8CAwEAAaAqMCgGCSqGSIb3DQEJDjEbMBkwFwYDVR0RBBAwDoIM
1860ZXhhbXBsZTIuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAHeeSW8C6SMVhWiMvPn7iz
1861FUHQedHRyPz6kTEfC01eNIbs0r4YghOAcm8PF67jncIXVrqgxo1uzq12qlV+0YYb
1862jps31IbQNOz0eFLYvij15ielmOYeQZZ/2vqaGi3geVobLc6Ki5tadnA/NhjTN33j
1863QcqDDic8riAOTbSQ6TH9KPTGJQOPk+taMpDGDHskIW0oME5iT2ewbhBHg6v/kSzy
1864tss2kBY5O7vo2COtbNcwX5Xp9S2LH9kVUKr0GIjuQjwbv5xl+GNdDey09W9EDACU
1865jcGV3++2wS4LN4h3CG4pWZ+LTXhm8ymhoWOapN95lfe3xLRAKFJwiLkGwS75++FW
1866-----END CERTIFICATE REQUEST-----";
1867        let mut csr_file = NamedTempFile::new().expect("Failed to create temp csr file");
1868        csr_file.write_all(csr_data).expect("Failed to write csr");
1869        let result = Csr::load_csr(csr_file.path());
1870        assert!(result.is_ok(), "Failed to load csr: {:?}", result.err());
1871    }
1872}