structured-cms 0.20.3

Borrowed CMS signatures, digital envelopes and timestamps with explicit providers and resource accounting
Documentation
//! Caller-driven RFC3161 request construction, without transport or randomness.

use crate::{Error, ErrorCode};
use structured_asn1::{
    Encoding, Oid, Reader,
    length::DerLength,
    memory::{MemoryError, OwnedVec, Work},
};
use structured_pkix::AlgorithmIdentifier;

const FIELD: &str = "timeStampReq.encoding";

/// One newly supplied request extension. Its value is the complete encoded
/// extension payload, not an OCTET STRING wrapper. Interpretation is external.
#[derive(Debug, Clone, Copy)]
pub struct RequestExtension<'a> {
    /// Registered extension identifier.
    pub identifier: Oid<'a>,
    /// Explicit criticality; false is omitted from DER.
    pub critical: bool,
    /// Original caller-provided payload; copied only into the final output.
    pub value: &'a [u8],
}

/// New request values. This is a construction input, not a computed imprint
/// receipt or evidence that the chosen hash is permitted. Compute the imprint
/// through the selected provider before supplying its bytes here.
///
/// Nonce bytes are an unsigned magnitude, generated by the caller's entropy
/// source. Empty means zero; None means absent. The encoder never invents a
/// nonce, clock, policy, default algorithm or extension interpretation.
pub struct NewRequest<'a> {
    /// Actual selected hash identifier with its original parameter provenance.
    pub algorithm: AlgorithmIdentifier<'a>,
    /// Finished message digest, with the width checked by the selected provider.
    pub hashed_message: &'a [u8],
    /// Explicit requested TSA policy, if any.
    pub policy: Option<Oid<'a>>,
    /// Newly generated unsigned nonce magnitude, without ASN.1 framing.
    pub nonce: Option<&'a [u8]>,
    /// Whether the response must carry the TSA certificate.
    pub cert_req: bool,
    /// New extensions, in caller order; duplicate identifiers are rejected.
    pub extensions: &'a [RequestExtension<'a>],
}

/// Construction refuses before returning any partially encoded request.
#[derive(Debug)]
pub enum RequestEncodingError {
    /// Original schema/session/DER processing cause.
    Processing(Error),
    /// Original PKIX parameter syntax or source refusal.
    Parameters(structured_pkix::Error),
    /// Capacity, arithmetic or cumulative work refusal.
    Resource(MemoryError),
}

impl NewRequest<'_> {
    /// Encode one complete DER `TimeStampReq`. The reader must be from the
    /// identifier's producing session, optionally rebound to current Work.
    /// New values are admitted through that joint execution; retained capacity
    /// keeps its actual allocation charge after the reader is dropped.
    ///
    /// RFC3161 §§2.4.1/3 and X.690 §11.5 require ordered DER fields and omission
    /// of DEFAULT FALSE. Parameters must already be DER-conformant; no opaque
    /// BER payload is silently rewritten or interpreted as a different hash.
    /// <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.1>
    pub fn encode_der(&self, parent: &Reader<'_>) -> Result<OwnedVec<u8>, RequestEncodingError> {
        if !self.algorithm.same_session(parent) {
            return Err(RequestEncodingError::Processing(Error::schema(
                ErrorCode::InvalidLocation,
                FIELD,
                self.algorithm.offset(),
            )));
        }
        let work = parent.execution();
        work.charge_work(1)
            .map_err(RequestEncodingError::Resource)?;
        parameters_der(self.algorithm, parent)?;
        let oid = self.algorithm.algorithm().as_bytes();
        let parameters = self.algorithm.parameters().unwrap_or(&[]);
        let algorithm = add(tlv(oid.len())?, parameters.len())?;
        let imprint = add(tlv(algorithm)?, tlv(self.hashed_message.len())?)?;
        let mut total = add(3, tlv(imprint)?)?;
        if let Some(policy) = self.policy {
            total = add(total, tlv(policy.as_bytes().len())?)?;
        }
        let nonce = if let Some(bytes) = self.nonce {
            work.charge_work(bytes.len() as u64)
                .map_err(RequestEncodingError::Resource)?;
            let first = bytes.iter().position(|b| *b != 0).unwrap_or(bytes.len());
            let bytes = &bytes[first..];
            let pad = bytes.is_empty() || bytes[0] & 0x80 != 0;
            total = add(total, tlv(add(bytes.len(), usize::from(pad))?)?)?;
            Some((bytes, pad))
        } else {
            None
        };
        if self.cert_req {
            total = add(total, 3)?;
        }
        let mut extensions_len = 0;
        for (index, extension) in self.extensions.iter().enumerate() {
            work.charge_work(1)
                .map_err(RequestEncodingError::Resource)?;
            for previous in &self.extensions[..index] {
                work.charge_work(add(
                    previous.identifier.as_bytes().len(),
                    extension.identifier.as_bytes().len(),
                )? as u64)
                    .map_err(RequestEncodingError::Resource)?;
                if previous.identifier == extension.identifier {
                    return Err(RequestEncodingError::Processing(Error::schema(
                        ErrorCode::DuplicateField,
                        FIELD,
                        self.algorithm.offset(),
                    )));
                }
            }
            extensions_len = add(extensions_len, tlv(extension_len(extension)?)?)?;
        }
        if !self.extensions.is_empty() {
            total = add(total, tlv(extensions_len)?)?;
        }
        let length = tlv(total)?;
        work.charge_work(length as u64)
            .map_err(RequestEncodingError::Resource)?;
        let mut bytes = work.vector();
        bytes
            .try_reserve_exact(length)
            .map_err(RequestEncodingError::Resource)?;
        header(&mut bytes, 0x30, total)?;
        push(&mut bytes, &[2, 1, 1])?;
        header(&mut bytes, 0x30, imprint)?;
        header(&mut bytes, 0x30, algorithm)?;
        value(&mut bytes, 6, oid)?;
        push(&mut bytes, parameters)?;
        value(&mut bytes, 4, self.hashed_message)?;
        if let Some(policy) = self.policy {
            value(&mut bytes, 6, policy.as_bytes())?;
        }
        if let Some((nonce, pad)) = nonce {
            header(&mut bytes, 2, nonce.len() + usize::from(pad))?;
            if pad {
                push(&mut bytes, &[0])?;
            }
            push(&mut bytes, nonce)?;
        }
        if self.cert_req {
            push(&mut bytes, &[1, 1, 255])?;
        }
        if !self.extensions.is_empty() {
            header(&mut bytes, 0xa0, extensions_len)?;
            for extension in self.extensions {
                header(&mut bytes, 0x30, extension_len(extension)?)?;
                value(&mut bytes, 6, extension.identifier.as_bytes())?;
                if extension.critical {
                    push(&mut bytes, &[1, 1, 255])?;
                }
                value(&mut bytes, 4, extension.value)?;
            }
        }
        debug_assert_eq!(bytes.len(), length);
        Ok(bytes.into_owned())
    }
}

fn parameters_der<'a, 'q>(
    algorithm: AlgorithmIdentifier<'a>,
    parent: &Reader<'q>,
) -> Result<(), RequestEncodingError>
where
    'a: 'q,
{
    let Some(mut parameters) = algorithm
        .parameters_reader_in(parent, FIELD)
        .map_err(RequestEncodingError::Parameters)?
    else {
        return Ok(());
    };
    let element = parameters
        .next_element()
        .map_err(|e| RequestEncodingError::Processing(Error::encoding(e, FIELD)))?
        .ok_or_else(|| {
            RequestEncodingError::Processing(Error::schema(
                ErrorCode::MissingField,
                FIELD,
                parameters.offset(),
            ))
        })?;
    // Re-read the strict header as well as its contents: using the BER Element
    // directly would retain a non-minimal length that DER never permits.
    let mut strict = parameters
        .reread(&element, Encoding::Der)
        .map_err(|e| RequestEncodingError::Processing(Error::encoding(e, FIELD)))?;
    let element = strict
        .next_element()
        .map_err(|e| RequestEncodingError::Processing(Error::encoding(e, FIELD)))?
        .ok_or_else(|| {
            RequestEncodingError::Processing(Error::schema(
                ErrorCode::MissingField,
                FIELD,
                strict.offset(),
            ))
        })?;
    structured_pkix::der::conforms(&strict, &element, FIELD)
        .map_err(RequestEncodingError::Parameters)
}

fn extension_len(extension: &RequestExtension<'_>) -> Result<usize, RequestEncodingError> {
    add(
        add(
            tlv(extension.identifier.as_bytes().len())?,
            if extension.critical { 3 } else { 0 },
        )?,
        tlv(extension.value.len())?,
    )
}
fn add(a: usize, b: usize) -> Result<usize, RequestEncodingError> {
    a.checked_add(b)
        .ok_or(RequestEncodingError::Resource(MemoryError::WorkExceeded))
}
fn tlv(content: usize) -> Result<usize, RequestEncodingError> {
    add(
        add(1, DerLength::new(content as u64).as_bytes().len())?,
        content,
    )
}
fn push<W: Work>(out: &mut OwnedVec<u8, W>, value: &[u8]) -> Result<(), RequestEncodingError> {
    out.try_extend_from_slice(value)
        .map_err(RequestEncodingError::Resource)
}
fn header<W: Work>(
    out: &mut OwnedVec<u8, W>,
    tag: u8,
    content: usize,
) -> Result<(), RequestEncodingError> {
    out.try_push(tag).map_err(RequestEncodingError::Resource)?;
    push(out, DerLength::new(content as u64).as_bytes())
}
fn value<W: Work>(
    out: &mut OwnedVec<u8, W>,
    tag: u8,
    value: &[u8],
) -> Result<(), RequestEncodingError> {
    header(out, tag, value.len())?;
    push(out, value)
}