synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
//! PKCS #10 Certificate Signing Request (RFC 2986) builder.
//!
//! Assembles DER-encoded CSRs using synta's own ASN.1 encoder.
//! The subject Name and SubjectPublicKeyInfo bytes are stored verbatim and
//! spliced into the `CertificationRequestInfo` via [`synta::RawDer`] — no
//! re-parse or re-encode at signing time.
//!
//! The builder is crypto-backend agnostic: callers pass a [`CertificateSigner`]
//! implementation to [`CsrBuilder::sign`].  An OpenSSL-backed signer is
//! available as `OpensslCertificateSigner` when the `openssl` feature
//! is enabled.
//!
//! # Optional `extensionRequest` attribute
//!
//! Use [`CsrBuilder::add_extension`] to attach X.509v3 extensions to the CSR
//! via the PKCS #9 `extensionRequest` attribute (OID 1.2.840.113549.1.9.14,
//! RFC 2985 §5.4.2).  When at least one extension is added, the builder
//! automatically encodes the attribute and includes it in the
//! `[0] IMPLICIT SET OF Attribute` field.
//!
//! # Example (OpenSSL signer)
//!
//! ```rust,ignore
//! use synta_certificate::{CsrBuilder, OpensslCertificateSigner};
//!
//! let csr_der = CsrBuilder::new()
//!     .subject_name(subject_der)
//!     .public_key_der(spki_der)
//!     .sign(&OpensslCertificateSigner::new(&key, "sha256"))
//!     .unwrap();
//! ```

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

use crate::builder::BuilderError;
use crate::crypto::CertificateSigner;
use crate::oids::PKCS9_EXTENSION_REQUEST;
use crate::Extension;

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

/// Errors that can occur while building or signing a CSR.
///
/// This is a type alias for [`BuilderError`] — the two builder types share the
/// same error variants (`MissingField`, `EncodeError`, `SignError`) and display
/// messages.
pub type CsrBuilderError = BuilderError;

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

/// Builder for DER-encoded PKCS #10 Certificate Signing Requests.
///
/// The subject Name and SubjectPublicKeyInfo bytes are stored verbatim and
/// spliced into the `CertificationRequestInfo` DER at signing time — no
/// re-parse or re-encode.  Extensions are encoded into a single
/// `extensionRequest` attribute automatically.
///
/// Call [`sign`](Self::sign) at the end, passing any [`CertificateSigner`]
/// implementation:
///
/// ```rust,ignore
/// use synta_certificate::{CsrBuilder, OpensslCertificateSigner};
///
/// let csr_der = CsrBuilder::new()
///     .subject_name(&subject_der)
///     .public_key_der(&spki_der)
///     .add_extension(oid, false, &value_der)
///     .sign(&OpensslCertificateSigner::new(&key, "sha256"))?;
/// ```
pub struct CsrBuilder {
    subject: Option<Vec<u8>>,
    spki: Option<Vec<u8>>,
    /// Extensions collected for the `extensionRequest` attribute.
    extensions: Vec<(ObjectIdentifier, bool, Vec<u8>)>,
}

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

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

    /// Set the subject Name from pre-encoded DER bytes.
    ///
    /// Pass the output of `Certificate::subject_raw_der` or
    /// `CertificationRequest::subject_raw_der` directly for zero re-encode.
    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
    }

    /// Add an X.509 v3 extension to the `extensionRequest` attribute.
    ///
    /// `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 by the encoder.
    ///
    /// When at least one extension is added, the builder automatically
    /// encodes all extensions into a single `extensionRequest` attribute
    /// (PKCS #9 OID 1.2.840.113549.1.9.14) in the `CertificationRequestInfo`.
    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 a requested 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.
    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 `CertificationRequestInfo` SEQUENCE.
    ///
    /// `sig_alg_der` must be the DER-encoded `AlgorithmIdentifier` SEQUENCE.
    /// Both subject and public key must have been set; returns
    /// `Err(CsrBuilderError::MissingField)` if either is absent.
    ///
    /// Most callers should use [`sign`](Self::sign) instead.
    pub fn build_cri(&self, sig_alg_der: &[u8]) -> Result<Vec<u8>, CsrBuilderError> {
        let subject = self
            .subject
            .as_deref()
            .ok_or(CsrBuilderError::MissingField("subject_name"))?;
        let spki = self
            .spki
            .as_deref()
            .ok_or(CsrBuilderError::MissingField("public_key"))?;

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

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

        // version INTEGER (0 = v1).
        enc.encode(&Integer::from_i64(0))?;

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

        // subjectPKInfo SubjectPublicKeyInfo — verbatim splice.
        enc.encode(&RawDer(spki))?;

        // attributes [0] IMPLICIT SET OF Attribute OPTIONAL
        // RFC 2986 §4: the field is OPTIONAL.  Emit the [0] IMPLICIT wrapper only
        // when there are attributes to include; omit it entirely when the list is
        // empty rather than emitting an empty [0] {} wrapper.
        if !self.extensions.is_empty() {
            enc.start_constructed_no_guard(Tag::context_specific_constructed(0))?;

            // Build the extensionRequest attribute:
            //   Attribute ::= SEQUENCE {
            //     attrType   OBJECT IDENTIFIER,   -- id-extensionRequest
            //     attrValues SET OF ANY           -- contains SEQUENCE OF Extension
            //   }
            enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SEQUENCE))?;

            // attrType = id-extensionRequest OID.
            let ext_req_oid = ObjectIdentifier::new(PKCS9_EXTENSION_REQUEST).map_err(|_| {
                CsrBuilderError::EncodeError("invalid PKCS9_EXTENSION_REQUEST OID constant".into())
            })?;
            enc.encode(&ext_req_oid)?;

            // attrValues SET OF ANY — one element: SEQUENCE OF Extension.
            enc.start_constructed_no_guard(Tag::universal_constructed(TAG_SET))?;
            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()?; // SET OF ANY (attrValues)
            enc.end_constructed()?; // Attribute SEQUENCE

            enc.end_constructed()?; // [0] IMPLICIT SET OF Attribute
        }

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

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

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

        // certificationRequestInfo — verbatim splice.
        enc.write_bytes(cri_der);

        // signatureAlgorithm AlgorithmIdentifier — verbatim splice.
        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| CsrBuilderError::EncodeError(e.to_string()))?;
        enc.encode(&sig_bstr)?;

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

    /// Sign the CSR using the given [`CertificateSigner`] and return the
    /// DER-encoded `CertificationRequest`.
    ///
    /// The signer is called first for its `AlgorithmIdentifier` DER, then
    /// with the encoded `CertificationRequestInfo` bytes.  Errors from the
    /// signer are wrapped as [`CsrBuilderError::SignError`].
    ///
    /// Both subject name and public key must have been set; returns
    /// [`CsrBuilderError::MissingField`] if either is absent.
    pub fn sign<S: CertificateSigner>(self, signer: &S) -> Result<Vec<u8>, CsrBuilderError> {
        let sig_alg_der = signer
            .signature_algorithm_der()
            .map_err(|e| CsrBuilderError::SignError(e.to_string()))?;
        let cri_der = self.build_cri(&sig_alg_der)?;
        let signature = signer
            .sign_tbs(&cri_der)
            .map_err(|e| CsrBuilderError::SignError(e.to_string()))?;
        Self::assemble(&cri_der, &sig_alg_der, &signature)
    }
}