structured-pkix 0.1.4

Borrowed X.509 certificates and provider-neutral certification path validation
Documentation
//! The one iterative BER base-string walker used by validation and codecs.

use super::{AttributeValue, Encoding, Error, ErrorCode, Located, OwnedVec, Result, Strings};
use structured_asn1::{Class, Element, Reader, Tag, universal};

pub(super) fn implicit<'a>(
    parent: &Reader<'a>,
    element: &Element<'a>,
    field: &'static str,
    bits: bool,
) -> Result<bool> {
    if element.tag().is_constructed() {
        if parent.encoding().is_der() {
            return Ok(false);
        }
        walk(parent, element, field, bits, None)?;
    } else if bits {
        return super::reads(parent.bit_string(element), field);
    }
    Ok(true)
}

pub(super) fn validate<'a>(
    parent: &Reader<'a>,
    element: &Element<'a>,
    field: &'static str,
    strings: Strings,
) -> Result<()> {
    let number = element.tag().number().expect("assigned universal string");
    let time = matches!(number, universal::UTC_TIME | universal::GENERALIZED_TIME);
    if (strings == Strings::Checked && !matches!(number, 3 | 4)) || time {
        let mut contents = parent.memory().vector();
        walk(parent, element, field, false, Some(&mut contents))?;
        let work = (contents.len() as u64).checked_mul(3).ok_or_else(|| {
            Error::resource(
                structured_asn1::memory::MemoryError::WorkExceeded,
                field,
                element.span().start,
            )
        })?;
        parent.charge(work).field(field)?;
        let value = AttributeValue {
            tag: Tag::primitive(number),
            contents: &contents,
            der: element.as_bytes(),
            offset: element.span().start,
        };
        let valid = if time {
            crate::time::ber_fits(&contents, number == universal::GENERALIZED_TIME)
        } else {
            matches!(value.encoding(), Encoding::Other | Encoding::Teletex)
                || value.validated_count(|_| true).is_some()
        };
        if !valid {
            return Err(Error::new(
                ErrorCode::MalformedValue,
                field,
                element.span().start,
            ));
        }
    } else {
        walk(parent, element, field, number == 3, None)?;
    }
    Ok(())
}

struct Frame<'a> {
    reader: Reader<'a>,
    unused: u8,
}

/// A known implicitly or universally tagged string has base-type children:
/// BIT STRING children for bits, OCTET STRING children for all other strings.
/// X.690 ยงยง8.6.4, 8.7.3.2, 8.23.3. Nested final padding propagates outward;
/// a following empty child does not make earlier padding permissible.
/// <https://www.itu.int/rec/T-REC-X.690-202102-I/en>
pub(crate) fn walk<'a>(
    parent: &Reader<'a>,
    element: &Element<'a>,
    field: &'static str,
    bits: bool,
    mut output: Option<&mut OwnedVec<u8>>,
) -> Result<()> {
    let mut frames = parent.memory().vector();
    frames
        .try_push(Frame {
            reader: parent.enter(element).field(field)?,
            unused: 0,
        })
        .map_err(|reason| Error::resource(reason, field, element.span().start))?;
    let base = if bits {
        universal::BIT_STRING
    } else {
        universal::OCTET_STRING
    };
    while let Some(frame) = frames.last_mut() {
        frame.reader.charge(1).field(field)?;
        let Some(child) = frame.reader.next_element().field(field)? else {
            let unused = frame.unused;
            frames.pop();
            if let Some(outer) = frames.last_mut() {
                outer.unused = unused;
            }
            continue;
        };
        if frame.unused != 0
            || child.tag().class() != Class::Universal
            || child.tag().number() != Some(base)
        {
            return Err(Error::new(
                ErrorCode::MalformedValue,
                field,
                child.span().start,
            ));
        }
        if child.tag().is_constructed() {
            let reader = frame.reader.enter(&child).field(field)?;
            frames
                .try_push(Frame { reader, unused: 0 })
                .map_err(|reason| Error::resource(reason, field, child.span().start))?;
        } else if bits {
            frame.reader.bit_string(&child).field(field)?;
            frame.unused = child.contents()[0];
        } else if let Some(bytes) = output.as_deref_mut() {
            // OwnedVec admits both copied octets and allocation growth before
            // executing them; the producing reader keeps its local ceiling.
            frame
                .reader
                .charge(child.contents().len() as u64)
                .field(field)?;
            bytes
                .try_extend_from_slice(child.contents())
                .map_err(|reason| Error::resource(reason, field, child.span().start))?;
        }
    }
    Ok(())
}