synta-certificate 0.2.0

X.509 certificate structures for synta ASN.1 library
Documentation
//! X.509 v3 certificate builder.
//!
//! Assembles DER-encoded X.509 certificates using synta's own ASN.1 encoder.
//! Pre-encoded Name, SubjectPublicKeyInfo, and extension-value bytes are stored
//! verbatim and spliced into the TBS via [`synta::RawDer`] — no re-parse or
//! re-encode at signing time.
//!
//! The builder is crypto-backend agnostic: callers pass a [`CertificateSigner`]
//! implementation to [`CertificateBuilder::sign`].  An OpenSSL-backed signer is
//! available as `OpensslCertificateSigner` when the `openssl` feature
//! is enabled.
//!
//! # Example (OpenSSL signer)
//!
//! ```rust,ignore
//! use synta_certificate::{CertificateBuilder, OpensslCertificateSigner};
//! use synta::{Integer, UtcTime};
//! use synta_certificate::Time;
//!
//! let cert_der = CertificateBuilder::new()
//!     .issuer_name(ca_cert_subject_der)
//!     .subject_name(csr_subject_der)
//!     .public_key_der(csr_spki_der)
//!     .serial_number(Integer::from_i64(1))
//!     .not_valid_before(Time::UtcTime(UtcTime::new(2024, 1, 1, 0, 0, 0).unwrap()))
//!     .not_valid_after(Time::UtcTime(UtcTime::new(2025, 1, 1, 0, 0, 0).unwrap()))
//!     .sign(&OpensslCertificateSigner::new(&ca_key, "sha256"))
//!     .unwrap();
//! ```

use core::fmt;

use synta::tag::TAG_SEQUENCE;
use synta::{Boolean, Integer, ObjectIdentifier, OctetStringRef, RawDer, Tag};

use crate::crypto::CertificateSigner;
use crate::{Extension, Time, Validity};

// ── Error ─────────────────────────────────────────────────────────────────────

/// Errors that can occur while building or signing a certificate.
#[derive(Debug)]
pub enum BuilderError {
    /// A required field was not set before signing.
    MissingField(&'static str),
    /// ASN.1 encoding failed.
    EncodeError(String),
    /// The signer returned an error.
    SignError(String),
}

impl fmt::Display for BuilderError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            BuilderError::MissingField(field) => {
                write!(f, "required field not set: {field}")
            }
            BuilderError::EncodeError(msg) => write!(f, "ASN.1 encode error: {msg}"),
            BuilderError::SignError(msg) => write!(f, "signing error: {msg}"),
        }
    }
}

impl std::error::Error for BuilderError {}

impl From<synta::Error> for BuilderError {
    fn from(e: synta::Error) -> Self {
        BuilderError::EncodeError(e.to_string())
    }
}

// ── Builder ───────────────────────────────────────────────────────────────────

/// Builder for DER-encoded X.509 v3 certificates.
///
/// All Name, SubjectPublicKeyInfo, and extension-value bytes are stored as-is
/// and spliced verbatim into the TBS DER at signing time — no re-parse or
/// re-encode.  The only allocation per field is the initial copy of the byte
/// slice into the builder's owned storage.
///
/// Each setter method consumes and returns `self`, enabling fluent chaining.
/// Call [`sign`](Self::sign) at the end, passing any [`CertificateSigner`]
/// implementation:
///
/// ```rust,ignore
/// use synta_certificate::{CertificateBuilder, OpensslCertificateSigner};
///
/// let cert_der = CertificateBuilder::new()
///     .issuer_name(&ca_subject_der)
///     .subject_name(&csr_subject_der)
///     .public_key_der(&csr_spki_der)
///     .serial_number(Integer::from_i64(42))
///     .not_valid_before(not_before)
///     .not_valid_after(not_after)
///     .sign(&OpensslCertificateSigner::new(&key, "sha256"))?;
/// ```
pub struct CertificateBuilder {
    issuer: Option<Vec<u8>>,
    subject: Option<Vec<u8>>,
    spki: Option<Vec<u8>>,
    serial: Option<Integer>,
    not_before: Option<Time>,
    not_after: Option<Time>,
    extensions: Vec<(ObjectIdentifier, bool, Vec<u8>)>,
}

impl Default for CertificateBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl CertificateBuilder {
    /// Create a new, empty builder.
    pub fn new() -> Self {
        Self {
            issuer: None,
            subject: None,
            spki: None,
            serial: None,
            not_before: None,
            not_after: None,
            extensions: Vec::new(),
        }
    }

    /// Set the issuer Name from pre-encoded DER bytes.
    ///
    /// The bytes are stored verbatim; pass the output of
    /// `Certificate::subject_raw_der` or `Certificate::issuer_raw_der`
    /// directly for zero re-encode.
    pub fn issuer_name(mut self, name_der: &[u8]) -> Self {
        self.issuer = Some(name_der.to_vec());
        self
    }

    /// Set the subject Name from pre-encoded DER bytes.
    pub fn subject_name(mut self, name_der: &[u8]) -> Self {
        self.subject = Some(name_der.to_vec());
        self
    }

    /// Set the SubjectPublicKeyInfo from pre-encoded SPKI DER bytes.
    ///
    /// Pass the output of `Certificate::subject_public_key_info_der` or
    /// `CertificationRequest::subject_public_key_info_der` directly.
    pub fn public_key_der(mut self, spki_der: &[u8]) -> Self {
        self.spki = Some(spki_der.to_vec());
        self
    }

    /// Set the certificate serial number.
    pub fn serial_number(mut self, serial: Integer) -> Self {
        self.serial = Some(serial);
        self
    }

    /// Set the `notBefore` validity time.
    pub fn not_valid_before(mut self, t: Time) -> Self {
        self.not_before = Some(t);
        self
    }

    /// Set the `notAfter` validity time.
    pub fn not_valid_after(mut self, t: Time) -> Self {
        self.not_after = Some(t);
        self
    }

    /// Add an X.509 v3 extension.
    ///
    /// `oid` is the extension OID.  `critical` marks it as critical.
    /// `value_der` is the raw DER of the extension value (the OCTET STRING
    /// wrapper is added automatically by the encoder).
    pub fn add_extension(
        mut self,
        oid: ObjectIdentifier,
        critical: bool,
        value_der: &[u8],
    ) -> Self {
        self.extensions.push((oid, critical, value_der.to_vec()));
        self
    }

    /// Add an X.509 v3 extension using OID components directly.
    ///
    /// Convenience wrapper around [`add_extension`](Self::add_extension) for
    /// callers that already have an OID as a `&[u32]` component slice (e.g.
    /// the `oids::*` constants).  Panics if `oid_components` does not form a
    /// valid OID.
    ///
    /// ```rust,ignore
    /// use synta_certificate::{CertificateBuilder, encode_basic_constraints, oids};
    ///
    /// builder.add_extension_oid(oids::BASIC_CONSTRAINTS, true, &bc_der)
    /// ```
    pub fn add_extension_oid(
        self,
        oid_components: &[u32],
        critical: bool,
        value_der: &[u8],
    ) -> Self {
        let oid = ObjectIdentifier::new(oid_components)
            .expect("add_extension_oid: invalid OID components");
        self.add_extension(oid, critical, value_der)
    }

    /// Build the DER-encoded TBSCertificate SEQUENCE.
    ///
    /// `sig_alg_der` must be the DER-encoded `AlgorithmIdentifier` SEQUENCE
    /// that will appear in both the TBS and the outer `Certificate`.  All
    /// required fields (issuer, subject, public key, serial number, validity)
    /// must have been set; returns `Err(BuilderError::MissingField)` if any
    /// is absent.
    ///
    /// Most callers should use [`sign`](Self::sign) instead.
    pub fn build_tbs(&self, sig_alg_der: &[u8]) -> Result<Vec<u8>, BuilderError> {
        let issuer = self
            .issuer
            .as_deref()
            .ok_or(BuilderError::MissingField("issuer_name"))?;
        let subject = self
            .subject
            .as_deref()
            .ok_or(BuilderError::MissingField("subject_name"))?;
        let spki = self
            .spki
            .as_deref()
            .ok_or(BuilderError::MissingField("public_key"))?;
        let serial = self
            .serial
            .as_ref()
            .ok_or(BuilderError::MissingField("serial_number"))?;
        let not_before = self
            .not_before
            .as_ref()
            .ok_or(BuilderError::MissingField("not_valid_before"))?;
        let not_after = self
            .not_after
            .as_ref()
            .ok_or(BuilderError::MissingField("not_valid_after"))?;

        let mut enc = synta::Encoder::with_capacity(
            synta::Encoding::Der,
            16 + issuer.len() + subject.len() + spki.len() + 64,
        );

        // Outer TBSCertificate SEQUENCE.
        enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;

        // version [0] EXPLICIT INTEGER v3 (value 2).
        enc.start_constructed_no_guard(Tag::context_specific_constructed(0))?;
        enc.encode(&Integer::from_i64(2))?;
        enc.end_constructed()?;

        // serialNumber INTEGER.
        enc.encode(serial)?;

        // signature AlgorithmIdentifier — verbatim splice.
        enc.write_bytes(sig_alg_der);

        // issuer Name — verbatim splice (zero re-encode).
        enc.encode(&RawDer(issuer))?;

        // validity Validity.
        enc.encode(&Validity {
            not_before: not_before.clone(),
            not_after: not_after.clone(),
        })?;

        // subject Name — verbatim splice (zero re-encode).
        enc.encode(&RawDer(subject))?;

        // subjectPublicKeyInfo — verbatim splice (zero re-encode).
        enc.encode(&RawDer(spki))?;

        // extensions [3] EXPLICIT SEQUENCE OF Extension (if any).
        if !self.extensions.is_empty() {
            enc.start_constructed_no_guard(Tag::context_specific_constructed(3))?;
            enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;
            for (oid, critical, value_bytes) in &self.extensions {
                let ext = Extension {
                    extn_id: oid.clone(),
                    critical: if *critical {
                        Some(Boolean::new(true))
                    } else {
                        None
                    },
                    extn_value: OctetStringRef::new(value_bytes),
                };
                enc.encode(&ext)?;
            }
            enc.end_constructed()?; // SEQUENCE OF Extension
            enc.end_constructed()?; // [3] EXPLICIT
        }

        enc.end_constructed()?; // TBSCertificate
        Ok(enc.finish()?)
    }

    /// Assemble a DER-encoded `Certificate` SEQUENCE from its three components.
    ///
    /// This is the low-level counterpart to [`build_tbs`](Self::build_tbs) for
    /// callers that handle their own signing.  The `signature` bytes must be
    /// the raw signature output (without the BIT STRING wrapper — this function
    /// adds it with `unused_bits = 0`).
    pub fn assemble(
        tbs_der: &[u8],
        sig_alg_der: &[u8],
        signature: &[u8],
    ) -> Result<Vec<u8>, BuilderError> {
        use synta::BitStringRef;

        let mut enc = synta::Encoder::with_capacity(
            synta::Encoding::Der,
            tbs_der.len() + sig_alg_der.len() + signature.len() + 8,
        );
        enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;

        // tbsCertificate — verbatim splice.
        enc.write_bytes(tbs_der);

        // signatureAlgorithm — verbatim splice (same DER as in TBS).
        enc.write_bytes(sig_alg_der);

        // signature BIT STRING (unused_bits = 0 || raw signature bytes).
        let sig_bstr = BitStringRef::new(signature, 0)
            .map_err(|e| BuilderError::EncodeError(e.to_string()))?;
        enc.encode(&sig_bstr)?;

        enc.end_constructed()?; // Certificate
        Ok(enc.finish()?)
    }

    /// Sign the certificate using the given [`CertificateSigner`] and return
    /// the DER-encoded `Certificate`.
    ///
    /// The signer is called first for its `AlgorithmIdentifier` DER (embedded
    /// in both the TBS and the outer `Certificate`), then with the encoded TBS
    /// bytes.  Errors from the signer are wrapped as [`BuilderError::SignError`].
    ///
    /// All required fields must have been set; returns
    /// [`BuilderError::MissingField`] if any is absent.
    pub fn sign<S: CertificateSigner>(self, signer: &S) -> Result<Vec<u8>, BuilderError> {
        let sig_alg_der = signer
            .signature_algorithm_der()
            .map_err(|e| BuilderError::SignError(e.to_string()))?;
        let tbs_der = self.build_tbs(&sig_alg_der)?;
        let signature = signer
            .sign_tbs(&tbs_der)
            .map_err(|e| BuilderError::SignError(e.to_string()))?;
        Self::assemble(&tbs_der, &sig_alg_der, &signature)
    }
}