Skip to main content

structured_pkix/ocsp/
nonce.rs

1//! Request nonce binding; randomness and request transport belong to the caller.
2
3use super::{BasicResponse, end};
4use crate::error::{Error, ErrorCode, Located, Result};
5use crate::oid;
6use structured_asn1::memory::Work;
7
8/// Caller-generated nonce for a new RFC9654 request. Construction validates
9/// size only; the caller must use a cryptographically strong random generator.
10#[derive(Debug, Clone, Copy)]
11pub struct RequestNonce<'a>(&'a [u8]);
12
13/// The actual sent nonce has a size outside the received wire schema.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct NonceSizeError {
16    /// Actual octet count; no input bytes are copied into diagnostics.
17    pub length: usize,
18}
19impl core::fmt::Display for NonceSizeError {
20    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21        write!(formatter, "nonce length {} is outside 1..128", self.length)
22    }
23}
24impl core::error::Error for NonceSizeError {}
25
26impl<'a> RequestNonce<'a> {
27    /// Retain an actual sent wire nonce for comparison, including legacy short
28    /// requests. RFC9654 §2.1 permits received sizes 1..128; generation of a
29    /// new compliant request must still use at least 32 strong random octets.
30    /// Invalid input is an error, not an absent request nonce.
31    /// <https://www.rfc-editor.org/rfc/rfc9654#section-2.1>
32    pub fn from_sent(bytes: &'a [u8]) -> core::result::Result<Self, NonceSizeError> {
33        if bytes.is_empty() || bytes.len() > 128 {
34            return Err(NonceSizeError {
35                length: bytes.len(),
36            });
37        }
38        Ok(Self(bytes))
39    }
40    /// Validate a newly generated request value. RFC9654 §2.1 requires new
41    /// requesters to use at least 32 octets; the
42    /// schema maximum is 128. This is not a check of the bytes' randomness.
43    /// <https://www.rfc-editor.org/rfc/rfc9654#section-2.1>
44    #[must_use]
45    pub fn new(bytes: &'a [u8]) -> Option<Self> {
46        if bytes.len() < 32 || bytes.len() > 128 {
47            return None;
48        }
49        Some(Self(bytes))
50    }
51
52    /// Original request bytes; no copy or encoding bridge.
53    #[must_use]
54    pub fn as_bytes(self) -> &'a [u8] {
55        self.0
56    }
57}
58
59/// Completed request-binding fact, not response authenticity or time validity.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum NonceBinding {
62    /// Neither request nor response carries a nonce.
63    NotRequested,
64    /// Responder supplied a nonce without a nonce-bearing request.
65    Unsolicited,
66    /// A nonce-bearing request received a response without one.
67    Missing,
68    /// The original request and response nonce bytes agree.
69    Matches,
70    /// The original request and response nonce bytes differ.
71    Differs,
72}
73
74pub(super) fn read<'a, W: Work>(
75    extension: &crate::Extension<'a>,
76    execution: &W,
77    field: &'static str,
78) -> Result<&'a [u8]> {
79    let mut reader = extension.reader_scoped(execution).field(field)?;
80    let value = reader
81        .expect(structured_asn1::Tag::primitive(
82            structured_asn1::universal::OCTET_STRING,
83        ))
84        .field(field)?;
85    end(&reader, field)?;
86    let bytes = value.contents();
87    // RFC9654 §2.1: encoded inner OCTET STRING, not raw extnValue bytes.
88    // https://www.rfc-editor.org/rfc/rfc9654#section-2.1
89    if bytes.is_empty() || bytes.len() > 128 {
90        return Err(Error::new(
91            ErrorCode::MalformedValue,
92            field,
93            extension.span().start,
94        ));
95    }
96    Ok(bytes)
97}
98
99pub(super) fn bind<L: Work, R: Work>(
100    request: Option<RequestNonce<'_>>,
101    response: Option<&[u8]>,
102    left: (&L, &'static str, u64),
103    right: (&R, &'static str, u64),
104) -> Result<NonceBinding> {
105    let (request, response) = match (request, response) {
106        (None, None) => return Ok(NonceBinding::NotRequested),
107        (None, Some(_)) => return Ok(NonceBinding::Unsolicited),
108        (Some(_), None) => return Ok(NonceBinding::Missing),
109        (Some(request), Some(response)) => (request.as_bytes(), response),
110    };
111    left.0
112        .charge_work(1)
113        .map_err(|reason| Error::resource(reason, left.1, left.2))?;
114    right
115        .0
116        .charge_work(1)
117        .map_err(|reason| Error::resource(reason, right.1, right.2))?;
118    if request.len() != response.len() {
119        return Ok(NonceBinding::Differs);
120    }
121    left.0
122        .charge_work(request.len() as u64)
123        .map_err(|reason| Error::resource(reason, left.1, left.2))?;
124    right
125        .0
126        .charge_work(response.len() as u64)
127        .map_err(|reason| Error::resource(reason, right.1, right.2))?;
128    Ok(if request == response {
129        NonceBinding::Matches
130    } else {
131        NonceBinding::Differs
132    })
133}
134
135impl<'a> BasicResponse<'a> {
136    /// Decode a response nonce, retaining the producing parser's local work and
137    /// depth ceilings. Received nonce syntax accepts 1..128 octets, including
138    /// legacy short nonces; this does not relax new-request construction.
139    pub fn nonce<W: Work>(&self, execution: &W) -> Result<Option<&'a [u8]>> {
140        match self.extensions.get(oid::OCSP_NONCE, execution)? {
141            Some(extension) => read(extension, execution, "ocspResponse.nonce").map(Some),
142            None => Ok(None),
143        }
144    }
145
146    /// Compare the actual request nonce only after authenticating this response.
147    /// Missing/unsolicited values are preserved for the caller's replay policy;
148    /// they are never replaced by a generated or default nonce.
149    pub fn bind_nonce<W: Work>(
150        &self,
151        request: Option<RequestNonce<'_>>,
152        execution: &W,
153    ) -> Result<NonceBinding> {
154        let source = self.algorithm.query_in(execution);
155        bind(
156            request,
157            self.nonce(execution)?,
158            (execution, "ocspResponse.nonce", self.span.start),
159            (&source, "ocspResponse.nonce", self.span.start),
160        )
161    }
162}