Skip to main content

structured_cms/timestamp/
response.rs

1//! Complete RFC3161 response framing, before request-bound token execution.
2//!
3//! Status text/flags are unauthenticated transport facts, not a TSA trust
4//! assertion. Network acquisition, polling, replay history and clocks are not
5//! performed here. RFC3161 §§2.4.2/3 require the status/token rules and DER wire
6//! messages. <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2>
7
8use super::Token;
9use crate::{Error, ErrorCode, Result, signer::required};
10use structured_asn1::memory::{OwnedVec, Work};
11use structured_asn1::{Element, Encoding, Parser, Reader, Span, Tag, universal};
12
13mod check;
14pub use check::CheckedResponse;
15
16const FIELD: &str = "timeStampResp";
17const STATUS: &str = "pkiStatusInfo";
18
19/// The six understood RFC3161 §2.4.2 status values. Unknown values refuse.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(u8)]
22pub enum Status {
23    /// A requested token is present; its cryptographic checks remain necessary.
24    Granted = 0,
25    /// A token with modifications is present, still subject to request checks.
26    GrantedWithMods = 1,
27    /// The request was rejected; this unauthenticated status is no proof.
28    Rejection = 2,
29    /// A response is not yet available; no polling is performed here.
30    Waiting = 3,
31    /// An unauthenticated warning of imminent revocation.
32    RevocationWarning = 4,
33    /// An unauthenticated notification of revocation.
34    RevocationNotification = 5,
35}
36impl Status {
37    /// Whether the response is required to carry a timestamp token.
38    #[must_use]
39    pub fn granted(self) -> bool {
40        matches!(self, Self::Granted | Self::GrantedWithMods)
41    }
42}
43
44/// Defined failure bit positions, not integer status codes.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46#[repr(u8)]
47pub enum Failure {
48    /// Unrecognized or unsupported algorithm.
49    BadAlgorithm = 0,
50    /// Transaction is not permitted or supported.
51    BadRequest = 2,
52    /// Submitted data has the wrong format.
53    BadDataFormat = 5,
54    /// TSA time source is unavailable.
55    TimeNotAvailable = 14,
56    /// Requested policy is unsupported.
57    UnacceptedPolicy = 15,
58    /// Requested extension is unsupported.
59    UnacceptedExtension = 16,
60    /// Additional information is unavailable or not understood.
61    AdditionalInformationUnavailable = 17,
62    /// Server system failure.
63    SystemFailure = 25,
64}
65
66/// Original named-bit field with all understood flags, without width truncation.
67#[derive(Debug, Clone, Copy)]
68pub struct FailureInfo<'a> {
69    element: Element<'a>,
70    flags: u32,
71}
72impl<'a> FailureInfo<'a> {
73    /// Constant-time scalar membership in the already validated named-bit value.
74    #[must_use]
75    pub fn contains(self, reason: Failure) -> bool {
76        self.flags & (1u32 << reason as u8) != 0
77    }
78    /// Original BIT STRING, including source range and padding octet.
79    #[must_use]
80    pub fn element(self) -> Element<'a> {
81        self.element
82    }
83    fn read(parent: &Reader<'a>, element: Element<'a>) -> Result<Self> {
84        const FIELD: &str = "pkiStatusInfo.failInfo";
85        const KNOWN: u32 = (1 << Failure::BadAlgorithm as u8)
86            | (1 << Failure::BadRequest as u8)
87            | (1 << Failure::BadDataFormat as u8)
88            | (1 << Failure::TimeNotAvailable as u8)
89            | (1 << Failure::UnacceptedPolicy as u8)
90            | (1 << Failure::UnacceptedExtension as u8)
91            | (1 << Failure::AdditionalInformationUnavailable as u8)
92            | (1 << Failure::SystemFailure as u8);
93        tag(&element, Tag::primitive(universal::BIT_STRING), FIELD)?;
94        let bits = parent
95            .bit_string(&element)
96            .map_err(|e| Error::encoding(e, FIELD))?;
97        let bytes = bits.as_bytes();
98        parent
99            .charge(1 + bytes.len() as u64)
100            .map_err(|e| Error::encoding(e, FIELD))?;
101        // X.690 §11.2.2 removes every trailing zero bit of a named-bit type.
102        // Ordinary BIT STRING padding validation alone does not enforce this.
103        // https://www.itu.int/rec/T-REC-X.690-202102-I/en
104        if let Some(last) = bytes.last()
105            && (*last == 0 || last.trailing_zeros() != u32::from(bits.unused_bits()))
106        {
107            return Err(Error::schema(
108                ErrorCode::MalformedValue,
109                FIELD,
110                element.span().start,
111            ));
112        }
113        if bytes.len() > 4 {
114            return Err(Error::schema(
115                ErrorCode::UnknownField,
116                FIELD,
117                element.span().start,
118            ));
119        }
120        let mut flags = 0u32;
121        for (index, byte) in bytes.iter().enumerate() {
122            flags |= u32::from(byte.reverse_bits()) << (index * 8);
123        }
124        // RFC3161 §2.4.2: clients MUST generate an error for failure values
125        // they do not understand; arbitrary-width high bits are not truncated.
126        if flags & !KNOWN != 0 {
127            return Err(Error::schema(
128                ErrorCode::UnknownField,
129                FIELD,
130                element.span().start,
131            ));
132        }
133        Ok(Self { element, flags })
134    }
135}
136
137/// One original UTF-8 reason string. It is neither joined nor normalized.
138#[derive(Debug, Clone, Copy)]
139pub struct StatusText<'a> {
140    value: &'a str,
141    span: Span,
142}
143impl<'a> StatusText<'a> {
144    /// Already validated borrowed UTF-8; display/output work belongs to caller.
145    #[must_use]
146    pub fn as_str(self) -> &'a str {
147        self.value
148    }
149    /// Original whole `UTF8String` range.
150    #[must_use]
151    pub fn span(self) -> Span {
152        self.span
153    }
154}
155
156/// Whole original status, before any signature or request comparison.
157#[derive(Debug)]
158pub struct StatusInfo<'a> {
159    status: Status,
160    text: Option<OwnedVec<StatusText<'a>>>,
161    failure: Option<FailureInfo<'a>>,
162    span: Span,
163}
164impl<'a> StatusInfo<'a> {
165    /// Original understood status; it does not authenticate the response.
166    #[must_use]
167    pub fn status(&self) -> Status {
168        self.status
169    }
170    /// Optional nonempty reason-string catalogue, preserving separate strings.
171    #[must_use]
172    pub fn text(&self) -> Option<&[StatusText<'a>]> {
173        self.text.as_deref()
174    }
175    /// Absent and present-empty failure fields remain distinct.
176    #[must_use]
177    pub fn failure(&self) -> Option<FailureInfo<'a>> {
178        self.failure
179    }
180    /// Original `PKIStatusInfo` enclosure.
181    #[must_use]
182    pub fn span(&self) -> Span {
183        self.span
184    }
185    fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
186        tag(element, Tag::constructed(universal::SEQUENCE), STATUS)?;
187        let mut inside = parent
188            .enter(element)
189            .map_err(|e| Error::encoding(e, STATUS))?;
190        let status_element = required(&mut inside, "pkiStatusInfo.status")?;
191        tag(
192            &status_element,
193            Tag::primitive(universal::INTEGER),
194            "pkiStatusInfo.status",
195        )?;
196        let status = inside
197            .integer(&status_element)
198            .map_err(|e| Error::encoding(e, "pkiStatusInfo.status"))?;
199        let status = match status.as_u64() {
200            Some(0) => Status::Granted,
201            Some(1) => Status::GrantedWithMods,
202            Some(2) => Status::Rejection,
203            Some(3) => Status::Waiting,
204            Some(4) => Status::RevocationWarning,
205            Some(5) => Status::RevocationNotification,
206            _ => {
207                return Err(Error::schema(
208                    ErrorCode::MalformedValue,
209                    "pkiStatusInfo.status",
210                    status_element.span().start,
211                ));
212            }
213        };
214        let mut next = inside
215            .next_element()
216            .map_err(|e| Error::encoding(e, STATUS))?;
217        let text = if let Some(element) = next
218            .as_ref()
219            .filter(|e| e.tag() == Tag::constructed(universal::SEQUENCE))
220        {
221            let values = read_text(&inside, element)?;
222            next = inside
223                .next_element()
224                .map_err(|e| Error::encoding(e, STATUS))?;
225            Some(values)
226        } else {
227            None
228        };
229        let failure = next
230            .map(|element| FailureInfo::read(&inside, element))
231            .transpose()?;
232        end(&inside, STATUS)?;
233        Ok(Self {
234            status,
235            text,
236            failure,
237            span: element.span(),
238        })
239    }
240}
241
242/// Complete DER response and its original optional token. Private fields keep
243/// the status/token association immutable; reading establishes no trust.
244///
245/// ```
246/// use std::sync::Arc;
247/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
248/// use structured_cms::timestamp::{Response, response::Status};
249/// let memory = Memory::new(Arc::new(MemoryAccount::new(4096, 65536)));
250/// let parser = Parser::der(memory);
251/// let mut reader = parser.read(&[0x30, 5, 0x30, 3, 2, 1, 2], 400).unwrap();
252/// let response = Response::read(&mut reader).unwrap();
253/// assert_eq!(response.status().status(), Status::Rejection);
254/// assert!(response.token().is_none()); // no signature evidence on failure
255/// ```
256#[derive(Debug)]
257pub struct Response<'a> {
258    status: StatusInfo<'a>,
259    token: Option<OriginalToken<'a>>,
260    element: Element<'a>,
261    parser: &'a Parser,
262    token_depth: u32,
263}
264
265#[derive(Debug)]
266struct OriginalToken<'a> {
267    value: Token<'a>,
268    element: Element<'a>,
269}
270impl<'a> Response<'a> {
271    /// Read exactly one complete response; refuse any trailing source bytes.
272    pub fn read(parent: &mut Reader<'a>) -> Result<Self> {
273        let element = parent
274            .expect(Tag::constructed(universal::SEQUENCE))
275            .map_err(|e| Error::encoding(e, FIELD))?;
276        end(parent, FIELD)?;
277        Self::read_element(parent, &element)
278    }
279    /// Read one original embedded response. The response's own DER contract
280    /// applies even in a BER carrier, without a replacement parser or account.
281    pub fn read_element(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
282        let mut strict = parent
283            .reread(element, Encoding::Der)
284            .map_err(|e| Error::encoding(e, FIELD))?;
285        let element = strict
286            .expect(Tag::constructed(universal::SEQUENCE))
287            .map_err(|e| Error::encoding(e, FIELD))?;
288        structured_pkix::der::conforms(&strict, &element, FIELD).map_err(Error::pkix)?;
289        let mut inside = strict
290            .enter(&element)
291            .map_err(|e| Error::encoding(e, FIELD))?;
292        let status_element = required(&mut inside, STATUS)?;
293        let status = StatusInfo::read(&inside, &status_element)?;
294        let token_element = inside
295            .next_element()
296            .map_err(|e| Error::encoding(e, FIELD))?;
297        end(&inside, FIELD)?;
298        // RFC3161 §2.4.2: statuses 0/1 MUST carry a token; every other status
299        // MUST NOT. This preflight runs before even decoding a purported token.
300        if status.status.granted() != token_element.is_some() {
301            return Err(Error::schema(
302                ErrorCode::MalformedValue,
303                FIELD,
304                element.span().start,
305            ));
306        }
307        let token = token_element
308            .as_ref()
309            .map(|element| {
310                let mut reader = inside
311                    .reread(element, Encoding::Der)
312                    .map_err(|e| Error::encoding(e, FIELD))?;
313                Token::read_from(&mut reader).map(|value| OriginalToken {
314                    value,
315                    element: *element,
316                })
317            })
318            .transpose()?;
319        Ok(Self {
320            status,
321            token,
322            element,
323            parser: parent.parser(),
324            token_depth: inside.depth(),
325        })
326    }
327    /// Original unauthenticated status metadata.
328    #[must_use]
329    pub fn status(&self) -> &StatusInfo<'a> {
330        &self.status
331    }
332    /// Original token, only present for granted/grantedWithMods.
333    #[must_use]
334    pub fn token(&self) -> Option<&Token<'a>> {
335        self.token.as_ref().map(|token| &token.value)
336    }
337    /// Original whole response; no re-encoded token or physical-offset reset.
338    #[must_use]
339    pub fn element(&self) -> &Element<'a> {
340        &self.element
341    }
342    /// Reopen the actual token at its original nested depth and current/source
343    /// ceilings. Absence is paid metadata, not an unlimited lookup fast path.
344    pub fn token_reader_in<'op>(&self, query: &Reader<'op>) -> Result<Option<Reader<'op>>>
345    where
346        'a: 'op,
347    {
348        self.token_in(query)
349            .map(|value| value.map(|(_, reader)| reader))
350    }
351
352    fn token_in<'op>(&self, query: &Reader<'op>) -> Result<Option<(&Token<'a>, Reader<'op>)>>
353    where
354        'a: 'op,
355    {
356        query
357            .execution()
358            .execution_for(self.parser)
359            .charge_work(1)
360            .map_err(|e| Error::resource(e, FIELD, self.element.span().start))?;
361        self.token
362            .as_ref()
363            .map(|token| {
364                self.parser
365                    .read_query_in(
366                        query,
367                        token.element.as_bytes(),
368                        token.element.span().start,
369                        Encoding::Der,
370                        self.token_depth,
371                    )
372                    .map(|reader| (&token.value, reader))
373                    .map_err(|e| Error::encoding(e, FIELD))
374            })
375            .transpose()
376    }
377}
378
379fn read_text<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<OwnedVec<StatusText<'a>>> {
380    const FIELD: &str = "pkiStatusInfo.statusString";
381    let mut inside = parent
382        .enter(element)
383        .map_err(|e| Error::encoding(e, FIELD))?;
384    let work = parent.execution();
385    let mut text = work.vector();
386    while let Some(element) = inside
387        .next_element()
388        .map_err(|e| Error::encoding(e, FIELD))?
389    {
390        tag(&element, Tag::primitive(universal::UTF8_STRING), FIELD)?;
391        inside
392            .charge(element.contents().len() as u64)
393            .map_err(|e| Error::encoding(e, FIELD))?;
394        let value = core::str::from_utf8(element.contents())
395            .map_err(|_| Error::schema(ErrorCode::MalformedValue, FIELD, element.span().start))?;
396        text.try_push(StatusText {
397            value,
398            span: element.span(),
399        })
400        .map_err(|e| Error::resource(e, FIELD, element.span().start))?;
401    }
402    // RFC2510 §3.1.1 (incorporated by RFC3161) defines SIZE (1..MAX).
403    // https://www.rfc-editor.org/rfc/rfc2510#section-3.1.1
404    if text.is_empty() {
405        return Err(Error::schema(
406            ErrorCode::MissingField,
407            FIELD,
408            element.span().start,
409        ));
410    }
411    Ok(text.into_owned())
412}
413fn tag(element: &Element<'_>, expected: Tag, field: &'static str) -> Result<()> {
414    if element.tag() == expected {
415        Ok(())
416    } else {
417        Err(Error::schema(
418            ErrorCode::UnexpectedTag,
419            field,
420            element.span().start,
421        ))
422    }
423}
424fn end(parent: &Reader<'_>, field: &'static str) -> Result<()> {
425    if parent.is_empty() {
426        Ok(())
427    } else {
428        Err(Error::schema(
429            ErrorCode::TrailingData,
430            field,
431            parent.offset(),
432        ))
433    }
434}