use super::{BasicResponse, end};
use crate::error::{Error, ErrorCode, Located, Result};
use crate::oid;
use structured_asn1::memory::{MemoryError, Work};
#[derive(Debug, Clone, Copy)]
pub struct RequestNonce<'a>(&'a [u8]);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonceSizeError {
pub length: usize,
}
impl core::fmt::Display for NonceSizeError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "nonce length {} is outside 1..128", self.length)
}
}
#[cfg(feature = "std")]
impl std::error::Error for NonceSizeError {}
impl<'a> RequestNonce<'a> {
pub fn from_sent(bytes: &'a [u8]) -> core::result::Result<Self, NonceSizeError> {
if bytes.is_empty() || bytes.len() > 128 {
return Err(NonceSizeError {
length: bytes.len(),
});
}
Ok(Self(bytes))
}
#[must_use]
pub fn new(bytes: &'a [u8]) -> Option<Self> {
if bytes.len() < 32 || bytes.len() > 128 {
return None;
}
Some(Self(bytes))
}
#[must_use]
pub fn as_bytes(self) -> &'a [u8] {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NonceBinding {
NotRequested,
Unsolicited,
Missing,
Matches,
Differs,
}
pub(super) fn read<'a, W: Work>(
extension: &crate::Extension<'a>,
execution: &W,
) -> Result<&'a [u8]> {
const FIELD: &str = "ocspResponse.nonce";
let mut reader = extension.reader_scoped(execution).field(FIELD)?;
let value = reader
.expect(structured_asn1::Tag::primitive(
structured_asn1::universal::OCTET_STRING,
))
.field(FIELD)?;
end(&reader, FIELD)?;
let bytes = value.contents();
if bytes.is_empty() || bytes.len() > 128 {
return Err(Error::new(
ErrorCode::MalformedValue,
FIELD,
extension.span().start,
));
}
Ok(bytes)
}
pub(super) fn bind<W: Work>(
request: Option<RequestNonce<'_>>,
response: Option<&[u8]>,
execution: &W,
at: u64,
) -> Result<NonceBinding> {
let (request, response) = match (request, response) {
(None, None) => return Ok(NonceBinding::NotRequested),
(None, Some(_)) => return Ok(NonceBinding::Unsolicited),
(Some(_), None) => return Ok(NonceBinding::Missing),
(Some(request), Some(response)) => (request.as_bytes(), response),
};
let work = u64::try_from(request.len().max(response.len()))
.ok()
.and_then(|n| n.checked_mul(2))
.and_then(|n| n.checked_add(1))
.ok_or_else(|| Error::resource(MemoryError::WorkExceeded, "ocspResponse.nonce", at))?;
execution
.charge_work(work)
.map_err(|reason| Error::resource(reason, "ocspResponse.nonce", at))?;
Ok(if request == response {
NonceBinding::Matches
} else {
NonceBinding::Differs
})
}
impl<'a> BasicResponse<'a> {
pub fn nonce<W: Work>(&self, execution: &W) -> Result<Option<&'a [u8]>> {
match self.extensions.get(oid::OCSP_NONCE, execution)? {
Some(extension) => read(extension, execution).map(Some),
None => Ok(None),
}
}
pub fn bind_nonce<W: Work>(
&self,
request: Option<RequestNonce<'_>>,
execution: &W,
) -> Result<NonceBinding> {
bind(request, self.nonce(execution)?, execution, self.span.start)
}
}