synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
//! Builder for CMP (RFC 9810 / RFC 4210) PKI messages.
//!
//! [`CMPMessageBuilder`] assembles a `PKIMessage` DER from sender/recipient
//! names, optional nonce fields, and a body payload.  Use
//! [`GeneralNameSpec`] to specify the sender and
//! recipient `GeneralName` values.
//!
//! The fluent builder style is the same consume-and-return pattern used by
//! [`CertificateBuilder`](crate::CertificateBuilder): each setter takes
//! `self` by value, mutates it, and returns it so calls can be chained.
//!
//! # Example
//!
//! ```rust,ignore
//! use synta_certificate::{CMPMessageBuilder, GeneralNameSpec};
//!
//! let msg_der = CMPMessageBuilder::new()
//!     .sender(GeneralNameSpec::rfc822("ca@example.com"))
//!     .recipient(GeneralNameSpec::directory_name(&subject_name_der))
//!     .transaction_id(b"\x01\x02\x03\x04")
//!     .body_pkiconf()
//!     .build()?;
//! ```

use synta::{Integer, Null, OctetStringRef, RawDer};

use crate::builder::BuilderError;
use crate::cmp_types::{PKIBody, PKIHeader, PKIMessage};
use crate::GeneralNameSpec;

// ── CMPBodySpec ───────────────────────────────────────────────────────────────

/// Body payload specification for [`CMPMessageBuilder`].
///
/// Use the setter methods on [`CMPMessageBuilder`] rather than constructing
/// this directly.
pub enum CMPBodySpec {
    /// `pkiConf [19]` — confirmation message, no content.
    Pkiconf,
    /// `ir [0]` — Initialization Request (`CertReqMessages` DER).
    Ir(Vec<u8>),
    /// `cr [2]` — Certification Request (`CertReqMessages` DER).
    Cr(Vec<u8>),
    /// `kur [7]` — Key Update Request (`CertReqMessages` DER).
    Kur(Vec<u8>),
    /// `p10cr [4]` — PKCS #10 Certification Request (CSR DER).
    P10cr(Vec<u8>),
    /// `genm [21]` — General Message (`GenMsgContent` DER).
    Genm(Vec<u8>),
}

// ── CMPMessageBuilder ─────────────────────────────────────────────────────────

/// Builder for a `PKIMessage` DER (RFC 9810 / RFC 4210).
///
/// Set sender, recipient, optional nonces, and a body payload, then call
/// [`build`](Self::build) to produce DER-encoded bytes.
///
/// ```rust,ignore
/// use synta_certificate::{CMPMessageBuilder, GeneralNameSpec};
///
/// let der = CMPMessageBuilder::new()
///     .sender(GeneralNameSpec::rfc822("ca@example.com"))
///     .recipient(GeneralNameSpec::directory_name(&name_der))
///     .body_pkiconf()
///     .build()?;
/// ```
pub struct CMPMessageBuilder {
    /// `pvno` integer: 2 = cmp2000 (RFC 4210), 3 = cmp2021 (RFC 9810).
    pvno: i64,
    /// Sender `GeneralName` specification.
    sender: Option<GeneralNameSpec>,
    /// Recipient `GeneralName` specification.
    recipient: Option<GeneralNameSpec>,
    /// Raw bytes for `transactionID` OCTET STRING value.
    transaction_id: Option<Vec<u8>>,
    /// Raw bytes for `senderNonce` OCTET STRING value.
    sender_nonce: Option<Vec<u8>>,
    /// Raw bytes for `recipNonce` OCTET STRING value.
    recip_nonce: Option<Vec<u8>>,
    /// Body payload to encode.
    body: CMPBodySpec,
}

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

impl CMPMessageBuilder {
    /// Create a new builder with `pvno` = 2 (cmp2000) and no fields set.
    ///
    /// The default body is `pkiConf`.  Call one of the `body_*` setters to
    /// choose a different body type before calling [`build`](Self::build).
    pub fn new() -> Self {
        Self {
            pvno: 2,
            sender: None,
            recipient: None,
            transaction_id: None,
            sender_nonce: None,
            recip_nonce: None,
            body: CMPBodySpec::Pkiconf,
        }
    }

    /// Set the `pvno` version integer (2 = cmp2000, 3 = cmp2021).
    pub fn pvno(mut self, pvno: i64) -> Self {
        self.pvno = pvno;
        self
    }

    /// Set the sender `GeneralName`.
    ///
    /// Use [`GeneralNameSpec::rfc822`], [`GeneralNameSpec::directory_name`],
    /// or another [`GeneralNameSpec`] constructor to build the argument.
    pub fn sender(mut self, gn: GeneralNameSpec) -> Self {
        self.sender = Some(gn);
        self
    }

    /// Set the recipient `GeneralName`.
    ///
    /// Use [`GeneralNameSpec::rfc822`], [`GeneralNameSpec::directory_name`],
    /// or another [`GeneralNameSpec`] constructor to build the argument.
    pub fn recipient(mut self, gn: GeneralNameSpec) -> Self {
        self.recipient = Some(gn);
        self
    }

    /// Set the `transactionID` from raw OCTET STRING value bytes.
    pub fn transaction_id(mut self, bytes: &[u8]) -> Self {
        self.transaction_id = Some(bytes.to_vec());
        self
    }

    /// Set the `senderNonce` from raw OCTET STRING value bytes.
    pub fn sender_nonce(mut self, bytes: &[u8]) -> Self {
        self.sender_nonce = Some(bytes.to_vec());
        self
    }

    /// Set the `recipNonce` from raw OCTET STRING value bytes.
    pub fn recip_nonce(mut self, bytes: &[u8]) -> Self {
        self.recip_nonce = Some(bytes.to_vec());
        self
    }

    /// Set the body to `pkiConf [19]` (confirmation, no content).
    pub fn body_pkiconf(mut self) -> Self {
        self.body = CMPBodySpec::Pkiconf;
        self
    }

    /// Set the body to `ir [0]` (Initialization Request) from DER-encoded
    /// `CertReqMessages` bytes.
    pub fn body_ir(mut self, cert_req_messages_der: &[u8]) -> Self {
        self.body = CMPBodySpec::Ir(cert_req_messages_der.to_vec());
        self
    }

    /// Set the body to `cr [2]` (Certification Request) from DER-encoded
    /// `CertReqMessages` bytes.
    pub fn body_cr(mut self, cert_req_messages_der: &[u8]) -> Self {
        self.body = CMPBodySpec::Cr(cert_req_messages_der.to_vec());
        self
    }

    /// Set the body to `kur [7]` (Key Update Request) from DER-encoded
    /// `CertReqMessages` bytes.
    pub fn body_kur(mut self, cert_req_messages_der: &[u8]) -> Self {
        self.body = CMPBodySpec::Kur(cert_req_messages_der.to_vec());
        self
    }

    /// Set the body to `p10cr [4]` (PKCS #10 Certification Request) from
    /// DER-encoded CSR bytes.
    pub fn body_p10cr(mut self, csr_der: &[u8]) -> Self {
        self.body = CMPBodySpec::P10cr(csr_der.to_vec());
        self
    }

    /// Set the body to `genm [21]` (General Message) from DER-encoded
    /// `GenMsgContent` bytes.
    pub fn body_genm(mut self, gen_msg_der: &[u8]) -> Self {
        self.body = CMPBodySpec::Genm(gen_msg_der.to_vec());
        self
    }

    /// Build and return the DER-encoded `PKIMessage` SEQUENCE.
    ///
    /// # Errors
    ///
    /// Returns [`BuilderError::EncodeError`] if sender or recipient are not
    /// set, if any `GeneralName` data is invalid, or if ASN.1 encoding fails.
    pub fn build(self) -> Result<Vec<u8>, BuilderError> {
        let sender_spec = self
            .sender
            .ok_or_else(|| BuilderError::EncodeError("sender is required".to_string()))?;
        let recipient_spec = self
            .recipient
            .ok_or_else(|| BuilderError::EncodeError("recipient is required".to_string()))?;
        let txid_bytes = self.transaction_id;
        let snonce_bytes = self.sender_nonce;
        let rnonce_bytes = self.recip_nonce;
        let body_spec = self.body;

        // Construct GeneralName values borrowing from the spec storage.
        let sender = sender_spec
            .to_general_name()
            .map_err(|e| BuilderError::EncodeError(e.to_string()))?;
        let recipient = recipient_spec
            .to_general_name()
            .map_err(|e| BuilderError::EncodeError(e.to_string()))?;

        // Wrap nonce/transactionID bytes as OctetStringRef borrows.
        let transaction_id = txid_bytes.as_deref().map(OctetStringRef::new);
        let sender_nonce = snonce_bytes.as_deref().map(OctetStringRef::new);
        let recip_nonce = rnonce_bytes.as_deref().map(OctetStringRef::new);

        // Construct PKIBody from the stored spec.
        let body = match body_spec {
            CMPBodySpec::Pkiconf => PKIBody::Pkiconf(Null),
            CMPBodySpec::Ir(ref bytes) => PKIBody::Ir(RawDer(bytes)),
            CMPBodySpec::Cr(ref bytes) => PKIBody::Cr(RawDer(bytes)),
            CMPBodySpec::Kur(ref bytes) => PKIBody::Kur(RawDer(bytes)),
            CMPBodySpec::P10cr(ref bytes) => PKIBody::P10cr(RawDer(bytes)),
            CMPBodySpec::Genm(ref bytes) => PKIBody::Genm(RawDer(bytes)),
        };

        let header = PKIHeader {
            pvno: Integer::from_i64(self.pvno),
            sender,
            recipient,
            message_time: None,
            protection_alg: None,
            sender_kid: None,
            recip_kid: None,
            transaction_id,
            sender_nonce,
            recip_nonce,
            free_text: None,
            general_info: None,
        };

        let msg = PKIMessage {
            header,
            body,
            protection: None,
            extra_certs: None,
        };

        msg.to_der()
            .map_err(|e| BuilderError::EncodeError(e.to_string()))
    }
}