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    /// Whole original `ContentInfo` carried by this exact response. This does
338    /// not prove successful request/token checks. Construction must associate
339    /// it with this response's own completed check, never another token receipt.
340    #[must_use]
341    pub fn token_element(&self) -> Option<&Element<'a>> {
342        self.token.as_ref().map(|token| &token.element)
343    }
344    /// Original whole response; no re-encoded token or physical-offset reset.
345    #[must_use]
346    pub fn element(&self) -> &Element<'a> {
347        &self.element
348    }
349    /// Reopen the actual token at its original nested depth and current/source
350    /// ceilings. Absence is paid metadata, not an unlimited lookup fast path.
351    pub fn token_reader_in<'op>(&self, query: &Reader<'op>) -> Result<Option<Reader<'op>>>
352    where
353        'a: 'op,
354    {
355        self.token_in(query)
356            .map(|value| value.map(|(_, reader)| reader))
357    }
358
359    fn token_in<'op>(&self, query: &Reader<'op>) -> Result<Option<(&Token<'a>, Reader<'op>)>>
360    where
361        'a: 'op,
362    {
363        query
364            .execution()
365            .execution_for(self.parser)
366            .charge_work(1)
367            .map_err(|e| Error::resource(e, FIELD, self.element.span().start))?;
368        self.token
369            .as_ref()
370            .map(|token| {
371                self.parser
372                    .read_query_in(
373                        query,
374                        token.element.as_bytes(),
375                        token.element.span().start,
376                        Encoding::Der,
377                        self.token_depth,
378                    )
379                    .map(|reader| (&token.value, reader))
380                    .map_err(|e| Error::encoding(e, FIELD))
381            })
382            .transpose()
383    }
384}
385
386fn read_text<'a>(parent: &Reader<'a>, element: &Element<'a>) -> Result<OwnedVec<StatusText<'a>>> {
387    const FIELD: &str = "pkiStatusInfo.statusString";
388    let mut inside = parent
389        .enter(element)
390        .map_err(|e| Error::encoding(e, FIELD))?;
391    let work = parent.execution();
392    let mut text = work.vector();
393    while let Some(element) = inside
394        .next_element()
395        .map_err(|e| Error::encoding(e, FIELD))?
396    {
397        tag(&element, Tag::primitive(universal::UTF8_STRING), FIELD)?;
398        inside
399            .charge(element.contents().len() as u64)
400            .map_err(|e| Error::encoding(e, FIELD))?;
401        let value = core::str::from_utf8(element.contents())
402            .map_err(|_| Error::schema(ErrorCode::MalformedValue, FIELD, element.span().start))?;
403        text.try_push(StatusText {
404            value,
405            span: element.span(),
406        })
407        .map_err(|e| Error::resource(e, FIELD, element.span().start))?;
408    }
409    // RFC2510 §3.1.1 (incorporated by RFC3161) defines SIZE (1..MAX).
410    // https://www.rfc-editor.org/rfc/rfc2510#section-3.1.1
411    if text.is_empty() {
412        return Err(Error::schema(
413            ErrorCode::MissingField,
414            FIELD,
415            element.span().start,
416        ));
417    }
418    Ok(text.into_owned())
419}
420fn tag(element: &Element<'_>, expected: Tag, field: &'static str) -> Result<()> {
421    if element.tag() == expected {
422        Ok(())
423    } else {
424        Err(Error::schema(
425            ErrorCode::UnexpectedTag,
426            field,
427            element.span().start,
428        ))
429    }
430}
431fn end(parent: &Reader<'_>, field: &'static str) -> Result<()> {
432    if parent.is_empty() {
433        Ok(())
434    } else {
435        Err(Error::schema(
436            ErrorCode::TrailingData,
437            field,
438            parent.offset(),
439        ))
440    }
441}