Skip to main content

structured_pades/material/vri/
status.rs

1//! VRI identity for the actual once-parsed CRL or basic OCSP signature.
2
3use super::{BindingError, NameError, name_for_ranges};
4use crate::{
5    error::{Error, ErrorKind},
6    material::{MaterialError, Parsed, Value, Vri},
7    model::admit,
8};
9use structured_asn1::memory::Work;
10use structured_pkix::{algorithm::digest::AsnDigest, signature::SignatureValue};
11
12/// A matching normative VRI name for an actual retained status signature.
13/// This does not authenticate that signature or assert certificate status.
14pub struct StatusBinding<'b, 'd> {
15    vri: &'b Vri,
16    item: usize,
17    signature: SignatureValue<'d>,
18}
19impl<'d> StatusBinding<'_, 'd> {
20    /// The matching original VRI in this same historical owner.
21    #[must_use]
22    pub fn vri(&self) -> &Vri {
23        self.vri
24    }
25    /// Original CRL/whole-OCSP item ordinal in the owning Validation.
26    #[must_use]
27    pub fn item(&self) -> usize {
28        self.item
29    }
30    /// Original status signature and its producing parser context.
31    #[must_use]
32    pub fn signature(&self) -> SignatureValue<'d> {
33        self.signature
34    }
35}
36
37/// Original syntax, source, provider or absence of a status signature.
38#[derive(Debug)]
39pub enum StatusError<'b, E> {
40    /// PDF/source/hash refusal, retaining the original cause.
41    Binding(BindingError<E>),
42    /// The actual retained item was unreadable on its first parse.
43    Unreadable(&'b MaterialError),
44    /// The known basic OCSP payload was unreadable on its first parse.
45    Ocsp(&'b structured_pkix::Error),
46    /// The chosen item is not a CRL or OCSP response.
47    NotStatus,
48    /// The original OCSP envelope has no known signed basic response.
49    NotSigned,
50}
51
52impl<'v, D: Copy + Eq, G: Copy + Eq> Parsed<'v, '_, D, G> {
53    /// Selects a status VRI by the SHA-1 of a primitive BER OCTET STRING
54    /// containing its original signature octets (EN319142-1 ยง5.4.2.2(b)).
55    /// The shortest definite length is also BER. No signature bytes are copied
56    /// and no status envelope or signature is reparsed.
57    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31914201/01.02.01_60/en_31914201v010201p.pdf>
58    ///
59    /// # Panics
60    /// Panics if the retained original file extent is internally invalid.
61    pub fn bind_status_vri<'b, 'p, P: AsnDigest<'p>>(
62        &'b self,
63        item: usize,
64        provider: &'p P,
65        operation: &impl Work,
66    ) -> Result<Option<StatusBinding<'b, 'v>>, StatusError<'b, P::Error>> {
67        let work = self.owner.session.query(operation);
68        let location = self.owner.items.get(item).map_or_else(
69            || {
70                crate::source::Location::new(
71                    crate::source::Source::File,
72                    0,
73                    self.owner.stamp.length,
74                )
75                .expect("original file")
76            },
77            crate::material::Item::location,
78        );
79        admit(&work, location, 1)
80            .map_err(|cause| StatusError::Binding(BindingError::Pdf(cause)))?;
81        let value = self.values.get(item).ok_or_else(|| {
82            StatusError::Binding(BindingError::Pdf(Error {
83                kind: ErrorKind::Foreign,
84                location,
85            }))
86        })?;
87        let signature = match value {
88            Value::Crl(value) => value.signature_value(),
89            Value::Ocsp {
90                basic: Ok(Some(value)),
91                ..
92            } => value.signature_value(),
93            Value::Ocsp {
94                basic: Ok(None), ..
95            } => return Err(StatusError::NotSigned),
96            Value::Ocsp {
97                basic: Err(cause), ..
98            } => return Err(StatusError::Ocsp(cause)),
99            Value::Unreadable(cause) => return Err(StatusError::Unreadable(cause)),
100            _ => return Err(StatusError::NotStatus),
101        };
102        if self.owner.vri_names.is_empty() {
103            return Ok(None);
104        }
105        let name = status_name(signature, provider, &work, location)
106            .map_err(|cause| StatusError::Binding(BindingError::Name(cause)))?
107            .ok_or(StatusError::Binding(BindingError::UnsupportedName))?;
108        let matched = self.owner.vri_names.get(&name, &work).map_err(|cause| {
109            StatusError::Binding(BindingError::Pdf(Error {
110                kind: ErrorKind::Resource(cause),
111                location,
112            }))
113        })?;
114        Ok(matched.map(|&ordinal| StatusBinding {
115            vri: &self.owner.vri[ordinal],
116            item,
117            signature,
118        }))
119    }
120}
121
122pub(super) fn status_name<'p, P: AsnDigest<'p>>(
123    signature: SignatureValue<'_>,
124    provider: &'p P,
125    operation: &impl Work,
126    location: crate::source::Location,
127) -> Result<Option<super::Name>, NameError<P::Error>> {
128    let work = signature.execution_in(operation);
129    admit(&work, location, 1).map_err(NameError::Pdf)?;
130    let bytes = signature.octets().ok_or(NameError::PaddedSignature)?;
131    let mut header = [0; core::mem::size_of::<usize>() + 2];
132    header[0] = 4;
133    let end = if bytes.len() < 128 {
134        header[1] = u8::try_from(bytes.len()).expect("short length");
135        2
136    } else {
137        let length = bytes.len().to_be_bytes();
138        let mut first = 0;
139        while length[first] == 0 {
140            first += 1;
141        }
142        let count = length.len() - first;
143        header[1] = 0x80 | u8::try_from(count).expect("machine-sized length");
144        header[2..2 + count].copy_from_slice(&length[first..]);
145        2 + count
146    };
147    name_for_ranges(provider, &mut [&header[..end], bytes], &work, location)
148}