use alloc::string::String;
use alloc::vec::Vec;
use super::{AnyPublicKey, CertSigner, Certificate, CrlReason, Error, Extension, Time, oid};
use crate::der::{
Reader, encode_bit_string, encode_context, encode_integer, encode_null, encode_octet_string,
encode_sequence, encode_tlv, oid_tlv, parse_oid, pem_decode, pem_encode, tag,
};
use crate::hash::{sha1, sha256, sha384, sha512};
use crate::rng::RngCore;
use crate::signature_registry::{SignaturePolicy, find_by_oid};
const PEM_LABEL: &str = "OCSP RESPONSE";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum OcspResponseStatus {
Successful = 0,
MalformedRequest = 1,
InternalError = 2,
TryLater = 3,
SigRequired = 5,
Unauthorized = 6,
}
impl OcspResponseStatus {
pub fn from_u8(v: u8) -> Result<Self, Error> {
match v {
0 => Ok(OcspResponseStatus::Successful),
1 => Ok(OcspResponseStatus::MalformedRequest),
2 => Ok(OcspResponseStatus::InternalError),
3 => Ok(OcspResponseStatus::TryLater),
5 => Ok(OcspResponseStatus::SigRequired),
6 => Ok(OcspResponseStatus::Unauthorized),
_ => Err(Error::Malformed),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OcspCertStatus {
Good,
Revoked {
revocation_time: Time,
reason: Option<CrlReason>,
},
Unknown,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OcspSingleResponse {
pub hash_alg_oid: Vec<u64>,
pub issuer_name_hash: Vec<u8>,
pub issuer_key_hash: Vec<u8>,
pub serial: Vec<u8>,
pub status: OcspCertStatus,
pub this_update: Time,
pub next_update: Option<Time>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OcspResponse {
der: Vec<u8>,
}
struct BasicParts<'a> {
tbs: &'a [u8],
sig_alg: Vec<u64>,
signature: &'a [u8],
certs_inner: Option<&'a [u8]>,
}
#[derive(Clone)]
pub struct OcspCheckOptions<'a> {
policy: &'a SignaturePolicy,
now: Option<&'a Time>,
nonce: Option<&'a [u8]>,
}
impl<'a> OcspCheckOptions<'a> {
pub fn new(policy: &'a SignaturePolicy) -> Self {
Self {
policy,
now: None,
nonce: None,
}
}
pub fn with_time(mut self, now: Option<&'a Time>) -> Self {
self.now = now;
self
}
pub fn with_nonce(mut self, nonce: &'a [u8]) -> Self {
self.nonce = Some(nonce);
self
}
}
impl OcspResponse {
pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
let mut r = Reader::new(&der);
r.read_sequence()?;
r.finish()?;
Ok(OcspResponse { der })
}
pub fn from_pem(pem: &str) -> Result<Self, Error> {
Ok(OcspResponse {
der: pem_decode(pem, PEM_LABEL)?,
})
}
pub fn to_der(&self) -> &[u8] {
&self.der
}
pub fn to_pem(&self) -> String {
pem_encode(PEM_LABEL, &self.der)
}
pub fn response_status(&self) -> Result<OcspResponseStatus, Error> {
let mut outer = Reader::new(&self.der);
let mut seq = outer.read_sequence()?;
let v = seq.read_tlv(0x0a)?; if v.len() != 1 {
return Err(Error::Malformed);
}
OcspResponseStatus::from_u8(v[0])
}
fn basic_response_der(&self) -> Result<Option<&[u8]>, Error> {
let mut outer = Reader::new(&self.der);
let mut seq = outer.read_sequence()?;
let status = seq.read_tlv(0x0a)?;
if status.len() != 1 || status[0] != 0 {
return Ok(None);
}
if seq.is_empty() {
return Ok(None);
}
let rb_tlv = seq.read_tlv(tag::context(0))?;
seq.finish()?;
let mut rb = Reader::new(rb_tlv);
let mut rb_seq = rb.read_sequence()?;
let resp_type = parse_oid(rb_seq.read_oid()?)?;
if resp_type.as_slice() != oid::ID_PKIX_OCSP_BASIC {
return Err(Error::UnsupportedAlgorithm);
}
let basic = rb_seq.read_octet_string()?;
rb_seq.finish()?;
rb.finish()?;
Ok(Some(basic))
}
fn basic_parts(&self) -> Result<BasicParts<'_>, Error> {
let basic = self.basic_response_der()?.ok_or(Error::Malformed)?;
let mut outer = Reader::new(basic);
let mut bocsp = outer.read_sequence()?;
let tbs = bocsp.read_element()?;
let mut alg = bocsp.read_sequence()?;
let sig_alg = parse_oid(alg.read_oid()?)?;
let signature = bocsp.read_bit_string()?;
let certs_inner = if !bocsp.is_empty() && bocsp.peek_tag() == Some(tag::context(0)) {
let body = bocsp.read_tlv(tag::context(0))?;
let mut sr = Reader::new(body);
let inner = sr.read_element()?; sr.finish()?;
Some(inner)
} else {
None
};
bocsp.finish()?;
Ok(BasicParts {
tbs,
sig_alg,
signature,
certs_inner,
})
}
pub fn signature_algorithm_oid(&self) -> Result<Vec<u64>, Error> {
Ok(self.basic_parts()?.sig_alg)
}
pub fn verify_signature_with(&self, key: &AnyPublicKey) -> Result<(), Error> {
let p = self.basic_parts()?;
key.verify(&p.sig_alg, p.tbs, p.signature)
}
pub fn verify_signature_with_policy(
&self,
key: &AnyPublicKey,
policy: &SignaturePolicy,
) -> Result<(), Error> {
let p = self.basic_parts()?;
let algo = find_by_oid(&p.sig_alg).ok_or(Error::Verification)?;
if !policy.permits(algo, &key.to_spki_der()) {
return Err(Error::Verification);
}
key.verify(&p.sig_alg, p.tbs, p.signature)
}
pub fn produced_at(&self) -> Result<Time, Error> {
let p = self.basic_parts()?;
let mut outer = Reader::new(p.tbs);
let mut td = outer.read_sequence()?;
if td.peek_tag() == Some(tag::context(0)) {
let _ = td.read_tlv(tag::context(0))?;
}
td.read_any()?;
read_generalized_time(&mut td)
}
pub fn delegated_responder_cert(&self) -> Result<Option<Certificate>, Error> {
let p = self.basic_parts()?;
let Some(inner) = p.certs_inner else {
return Ok(None);
};
let mut r = Reader::new(inner);
let mut list = r.read_sequence()?;
if list.is_empty() {
return Ok(None);
}
let first = list.read_element()?;
Ok(Some(Certificate::from_der(first.to_vec())?))
}
pub fn responses(&self) -> Result<Vec<OcspSingleResponse>, Error> {
let p = self.basic_parts()?;
let mut out = Vec::new();
let mut outer = Reader::new(p.tbs);
let mut td = outer.read_sequence()?;
if td.peek_tag() == Some(tag::context(0)) {
let _ = td.read_tlv(tag::context(0))?;
}
td.read_any()?;
td.read_any()?;
let responses_tlv = td.read_element()?;
let mut rr = Reader::new(responses_tlv);
let mut rseq = rr.read_sequence()?;
while !rseq.is_empty() {
out.push(read_single_response(&mut rseq)?);
}
Ok(out)
}
pub fn nonce(&self) -> Result<Option<Vec<u8>>, Error> {
let p = self.basic_parts()?;
let mut outer = Reader::new(p.tbs);
let mut td = outer.read_sequence()?;
if td.peek_tag() == Some(tag::context(0)) {
let _ = td.read_tlv(tag::context(0))?; }
td.read_any()?; td.read_any()?; td.read_any()?; if td.is_empty() || td.peek_tag() != Some(tag::context(1)) {
return Ok(None);
}
let wrapper = td.read_tlv(tag::context(1))?;
let mut outer_ext = Reader::new(wrapper);
let mut exts = outer_ext.read_sequence()?;
while !exts.is_empty() {
let mut ext = exts.read_sequence()?;
let id = parse_oid(ext.read_oid()?)?;
if ext.peek_tag() == Some(tag::BOOLEAN) {
ext.read_boolean()?;
}
let value = ext.read_octet_string()?;
if id.as_slice() == oid::ID_PKIX_OCSP_NONCE {
let mut nr = Reader::new(value);
let inner = nr.read_octet_string()?;
nr.finish()?;
return Ok(Some(inner.to_vec()));
}
}
Ok(None)
}
pub fn check_for_cert(
&self,
leaf: &Certificate,
issuer: &Certificate,
now: Option<&Time>,
) -> Result<OcspCertStatus, Error> {
let policy = SignaturePolicy::modern();
self.check_for_cert_with_options(
leaf,
issuer,
&OcspCheckOptions::new(&policy).with_time(now),
)
}
pub fn check_for_cert_with_options(
&self,
leaf: &Certificate,
issuer: &Certificate,
opts: &OcspCheckOptions<'_>,
) -> Result<OcspCertStatus, Error> {
let policy = opts.policy;
let now = opts.now;
let issuer_key = issuer.subject_public_key()?;
if self
.verify_signature_with_policy(&issuer_key, policy)
.is_err()
{
let responder = self
.delegated_responder_cert()?
.ok_or(Error::Verification)?;
verify_cert_signature_with_policy(&responder, &issuer_key, policy)?;
let ekus = responder.extended_key_usages()?;
if !ekus.iter().any(|o| o.as_slice() == oid::ID_KP_OCSP_SIGNING) {
return Err(Error::Verification);
}
if let Some((true, _)) = responder.basic_constraints()? {
return Err(Error::Verification);
}
check_responder_critical_extensions(&responder)?;
if let Some(now) = now
&& !responder.validity()?.accepts(now)
{
return Err(Error::Verification);
}
let responder_key = responder.subject_public_key()?;
self.verify_signature_with_policy(&responder_key, policy)?;
}
let single = self
.find_response_for(leaf, issuer)?
.ok_or(Error::Malformed)?;
if let Some(now) = now {
let now_u = now.to_unix();
let this_update = single
.this_update
.to_unix_checked()
.ok_or(Error::Malformed)?;
if this_update > now_u {
return Err(Error::Malformed);
}
if let Some(nu) = &single.next_update {
let next_update = nu.to_unix_checked().ok_or(Error::Malformed)?;
if now_u >= next_update {
return Err(Error::Malformed);
}
}
}
if let Some(expected_nonce) = opts.nonce {
match self.nonce()? {
Some(got) if got.as_slice() == expected_nonce => {}
_ => return Err(Error::Verification),
}
}
Ok(single.status)
}
pub fn check_for_cert_with_nonce(
&self,
leaf: &Certificate,
issuer: &Certificate,
now: Option<&Time>,
expected_nonce: &[u8],
) -> Result<OcspCertStatus, Error> {
let policy = SignaturePolicy::modern();
self.check_for_cert_with_options(
leaf,
issuer,
&OcspCheckOptions::new(&policy)
.with_time(now)
.with_nonce(expected_nonce),
)
}
pub fn find_response_for(
&self,
leaf: &Certificate,
issuer: &Certificate,
) -> Result<Option<OcspSingleResponse>, Error> {
let issuer_name_tlv = issuer.subject_der()?;
let issuer_key_bits = issuer.subject_public_key_bits()?;
let serial = leaf.serial_bytes()?;
let serial_magnitude = strip_leading_sign_zero(serial);
for r in self.responses()? {
let Some((nh, kh)) = hash_pair(&r.hash_alg_oid, issuer_name_tlv, issuer_key_bits)
else {
continue;
};
if r.issuer_name_hash != nh || r.issuer_key_hash != kh {
continue;
}
if strip_leading_sign_zero(&r.serial) != serial_magnitude {
continue;
}
return Ok(Some(r));
}
Ok(None)
}
}
fn read_generalized_time(r: &mut Reader<'_>) -> Result<Time, Error> {
let (t, value) = r.read_any()?;
if t != tag::GENERALIZED_TIME || value.len() != 15 {
return Err(Error::Malformed);
}
let s = core::str::from_utf8(value).map_err(|_| Error::Malformed)?;
Ok(Time::from_repr(s))
}
fn read_single_response(reader: &mut Reader<'_>) -> Result<OcspSingleResponse, Error> {
let entry = reader.read_element()?;
let mut r = Reader::new(entry);
let mut s = r.read_sequence()?;
let mut cert_id = s.read_sequence()?;
let mut hash_alg = cert_id.read_sequence()?;
let hash_alg_oid = parse_oid(hash_alg.read_oid()?)?;
if !hash_alg.is_empty() {
hash_alg.read_null()?;
}
hash_alg.finish()?;
let issuer_name_hash = cert_id.read_octet_string()?.to_vec();
let issuer_key_hash = cert_id.read_octet_string()?.to_vec();
let serial = cert_id.read_unsigned_integer_bytes()?.to_vec();
cert_id.finish()?;
let (status_tag, status_body) = s.read_any()?;
let status = match status_tag {
0x80 => {
if !status_body.is_empty() {
return Err(Error::Malformed);
}
OcspCertStatus::Good
}
0xa1 => {
let mut ri = Reader::new(status_body);
let revocation_time = read_generalized_time(&mut ri)?;
let mut reason = None;
if !ri.is_empty() && ri.peek_tag() == Some(tag::context(0)) {
let body = ri.read_tlv(tag::context(0))?;
let mut br = Reader::new(body);
let enum_body = br.read_tlv(0x0a)?;
if enum_body.len() != 1 {
return Err(Error::Malformed);
}
reason = Some(CrlReason::from_u8(enum_body[0])?);
br.finish()?;
}
ri.finish()?;
OcspCertStatus::Revoked {
revocation_time,
reason,
}
}
0x82 => {
if !status_body.is_empty() {
return Err(Error::Malformed);
}
OcspCertStatus::Unknown
}
_ => return Err(Error::Malformed),
};
let this_update = read_generalized_time(&mut s)?;
let mut next_update = None;
if !s.is_empty() && s.peek_tag() == Some(tag::context(0)) {
let body = s.read_tlv(tag::context(0))?;
let mut nr = Reader::new(body);
next_update = Some(read_generalized_time(&mut nr)?);
nr.finish()?;
}
Ok(OcspSingleResponse {
hash_alg_oid,
issuer_name_hash,
issuer_key_hash,
serial,
status,
this_update,
next_update,
})
}
fn hash_pair(hash_alg_oid: &[u64], name_tlv: &[u8], key_bits: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
if hash_alg_oid == oid::ID_SHA1 {
Some((sha1(name_tlv).to_vec(), sha1(key_bits).to_vec()))
} else if hash_alg_oid == oid::ID_SHA256 {
Some((sha256(name_tlv).to_vec(), sha256(key_bits).to_vec()))
} else if hash_alg_oid == oid::ID_SHA384 {
Some((sha384(name_tlv).to_vec(), sha384(key_bits).to_vec()))
} else if hash_alg_oid == oid::ID_SHA512 {
Some((sha512(name_tlv).to_vec(), sha512(key_bits).to_vec()))
} else {
None
}
}
fn strip_leading_sign_zero(bytes: &[u8]) -> &[u8] {
if bytes.len() > 1 && bytes[0] == 0x00 {
&bytes[1..]
} else {
bytes
}
}
fn verify_cert_signature_with_policy(
cert: &Certificate,
issuer_key: &AnyPublicKey,
policy: &SignaturePolicy,
) -> Result<(), Error> {
let sig_alg = cert.signature_algorithm_oid()?;
let algo = find_by_oid(&sig_alg).ok_or(Error::Verification)?;
if !policy.permits(algo, &issuer_key.to_spki_der()) {
return Err(Error::Verification);
}
cert.verify_signature_with(issuer_key)
}
fn check_responder_critical_extensions(cert: &Certificate) -> Result<(), Error> {
for o in cert.critical_extension_oids()? {
let bytes = o.as_slice();
if bytes == oid::BASIC_CONSTRAINTS
|| bytes == oid::KEY_USAGE
|| bytes == oid::EXT_KEY_USAGE
|| bytes == oid::SUBJECT_ALT_NAME
|| bytes == oid::ID_PKIX_OCSP_NOCHECK
{
continue;
}
return Err(Error::Verification);
}
Ok(())
}
fn ocsp_hash_algid(arcs: &[u64]) -> Vec<u8> {
let mut body = oid_tlv(arcs);
body.extend_from_slice(&encode_null());
encode_sequence(&body)
}
#[derive(Clone, Debug)]
pub struct OcspResponseBuilder {
issuer_name_tlv: Vec<u8>,
issuer_key_bits: Vec<u8>,
serial: Vec<u8>,
status: OcspCertStatus,
this_update: Time,
next_update: Option<Time>,
produced_at: Option<Time>,
hash_alg_oid: &'static [u64],
responder_id_by_key: bool,
delegated_responder_cert: Option<Vec<u8>>,
nonce: Option<Vec<u8>>,
}
impl OcspResponseBuilder {
pub fn good(
leaf: &Certificate,
issuer: &Certificate,
this_update: Time,
next_update: Option<Time>,
) -> Result<Self, Error> {
Self::with_status(leaf, issuer, this_update, next_update, OcspCertStatus::Good)
}
pub fn revoked(
leaf: &Certificate,
issuer: &Certificate,
this_update: Time,
next_update: Option<Time>,
revocation_time: Time,
reason: Option<CrlReason>,
) -> Result<Self, Error> {
Self::with_status(
leaf,
issuer,
this_update,
next_update,
OcspCertStatus::Revoked {
revocation_time,
reason,
},
)
}
fn with_status(
leaf: &Certificate,
issuer: &Certificate,
this_update: Time,
next_update: Option<Time>,
status: OcspCertStatus,
) -> Result<Self, Error> {
Ok(OcspResponseBuilder {
issuer_name_tlv: issuer.subject_der()?.to_vec(),
issuer_key_bits: issuer.subject_public_key_bits()?.to_vec(),
serial: leaf.serial_bytes()?.to_vec(),
status,
this_update,
next_update,
produced_at: None,
hash_alg_oid: oid::ID_SHA1,
responder_id_by_key: true,
delegated_responder_cert: None,
nonce: None,
})
}
pub fn produced_at(mut self, t: Time) -> Self {
self.produced_at = Some(t);
self
}
pub fn hash_algorithm(mut self, arcs: &'static [u64]) -> Self {
self.hash_alg_oid = arcs;
self
}
pub fn responder_id_by_name(mut self) -> Self {
self.responder_id_by_key = false;
self
}
pub fn delegated_responder_cert(mut self, responder_cert_der: Vec<u8>) -> Self {
self.delegated_responder_cert = Some(responder_cert_der);
self
}
pub fn nonce(mut self, nonce: &[u8]) -> Self {
self.nonce = Some(nonce.to_vec());
self
}
pub fn sign(self, signer: &CertSigner<'_>) -> Result<OcspResponse, Error> {
let (name_hash, key_hash) = hash_pair(
self.hash_alg_oid,
&self.issuer_name_tlv,
&self.issuer_key_bits,
)
.ok_or(Error::UnsupportedAlgorithm)?;
let mut cert_id_body = Vec::new();
cert_id_body.extend_from_slice(&ocsp_hash_algid(self.hash_alg_oid));
cert_id_body.extend_from_slice(&encode_octet_string(&name_hash));
cert_id_body.extend_from_slice(&encode_octet_string(&key_hash));
cert_id_body.extend_from_slice(&encode_integer(&self.serial));
let cert_id = encode_sequence(&cert_id_body);
let cert_status = match &self.status {
OcspCertStatus::Good => encode_tlv(0x80, &[]),
OcspCertStatus::Revoked {
revocation_time,
reason,
} => {
let mut ri = Vec::new();
ri.extend_from_slice(&revocation_time.to_generalized_time());
if let Some(r) = reason {
let enumerated = encode_tlv(0x0a, &[*r as u8]);
ri.extend_from_slice(&encode_context(0, &enumerated));
}
encode_tlv(0xa1, &ri)
}
OcspCertStatus::Unknown => encode_tlv(0x82, &[]),
};
let mut sr = Vec::new();
sr.extend_from_slice(&cert_id);
sr.extend_from_slice(&cert_status);
sr.extend_from_slice(&self.this_update.to_generalized_time());
if let Some(nu) = &self.next_update {
sr.extend_from_slice(&encode_context(0, &nu.to_generalized_time()));
}
let single_response = encode_sequence(&sr);
let responses = encode_sequence(&single_response);
let responder_id = if self.responder_id_by_key {
let kh = sha1(&self.issuer_key_bits).to_vec();
encode_context(2, &encode_octet_string(&kh))
} else {
encode_context(1, &self.issuer_name_tlv)
};
let produced_at = self.produced_at.as_ref().unwrap_or(&self.this_update);
let mut td = Vec::new();
td.extend_from_slice(&responder_id);
td.extend_from_slice(&produced_at.to_generalized_time());
td.extend_from_slice(&responses);
if let Some(nonce_bytes) = &self.nonce {
if nonce_bytes.is_empty() || nonce_bytes.len() > 32 {
return Err(Error::Malformed);
}
let inner = encode_octet_string(nonce_bytes);
let nonce_ext = Extension {
oid: oid::ID_PKIX_OCSP_NONCE.to_vec(),
critical: false,
value: inner,
}
.to_der();
let exts_seq = encode_sequence(&nonce_ext);
td.extend_from_slice(&encode_context(1, &exts_seq));
}
let tbs_response_data = encode_sequence(&td);
let sig_algid = signer.algorithm_identifier();
let signature = signer.sign(&tbs_response_data)?;
let mut bocsp = Vec::new();
bocsp.extend_from_slice(&tbs_response_data);
bocsp.extend_from_slice(&sig_algid);
bocsp.extend_from_slice(&encode_bit_string(&signature));
if let Some(cert_der) = &self.delegated_responder_cert {
let certs_seq = encode_sequence(cert_der);
bocsp.extend_from_slice(&encode_context(0, &certs_seq));
}
let basic = encode_sequence(&bocsp);
let mut rb = Vec::new();
rb.extend_from_slice(&oid_tlv(oid::ID_PKIX_OCSP_BASIC));
rb.extend_from_slice(&encode_octet_string(&basic));
let response_bytes = encode_sequence(&rb);
let status = encode_tlv(0x0a, &[OcspResponseStatus::Successful as u8]);
let mut out = Vec::new();
out.extend_from_slice(&status);
out.extend_from_slice(&encode_context(0, &response_bytes));
Ok(OcspResponse {
der: encode_sequence(&out),
})
}
}
#[derive(Clone, Debug)]
pub struct OcspRequest {
der: Vec<u8>,
nonce: Option<Vec<u8>>,
}
#[derive(Clone, Debug)]
pub struct OcspRequestBuilder {
issuer_name_tlv: Vec<u8>,
issuer_key_bits: Vec<u8>,
serial: Vec<u8>,
hash_alg_oid: &'static [u64],
nonce: Option<Vec<u8>>,
}
impl OcspRequestBuilder {
pub fn new(leaf: &Certificate, issuer: &Certificate) -> Result<Self, Error> {
Ok(OcspRequestBuilder {
issuer_name_tlv: issuer.subject_der()?.to_vec(),
issuer_key_bits: issuer.subject_public_key_bits()?.to_vec(),
serial: leaf.serial_bytes()?.to_vec(),
hash_alg_oid: oid::ID_SHA1,
nonce: None,
})
}
pub fn hash_algorithm(mut self, arcs: &'static [u64]) -> Self {
self.hash_alg_oid = arcs;
self
}
pub fn nonce(mut self, nonce: &[u8]) -> Self {
self.nonce = Some(nonce.to_vec());
self
}
pub fn random_nonce<R: RngCore>(mut self, rng: &mut R) -> Self {
let mut buf = [0u8; 16];
rng.fill_bytes(&mut buf);
self.nonce = Some(buf.to_vec());
self
}
pub fn build(self) -> Result<OcspRequest, Error> {
let (name_hash, key_hash) = hash_pair(
self.hash_alg_oid,
&self.issuer_name_tlv,
&self.issuer_key_bits,
)
.ok_or(Error::UnsupportedAlgorithm)?;
let mut cert_id_body = Vec::new();
cert_id_body.extend_from_slice(&ocsp_hash_algid(self.hash_alg_oid));
cert_id_body.extend_from_slice(&encode_octet_string(&name_hash));
cert_id_body.extend_from_slice(&encode_octet_string(&key_hash));
cert_id_body.extend_from_slice(&encode_integer(&self.serial));
let cert_id = encode_sequence(&cert_id_body);
let request_one = encode_sequence(&cert_id);
let request_list = encode_sequence(&request_one);
let mut tbs = Vec::new();
tbs.extend_from_slice(&request_list);
if let Some(nonce_bytes) = &self.nonce {
if nonce_bytes.is_empty() || nonce_bytes.len() > 32 {
return Err(Error::Malformed);
}
let inner = encode_octet_string(nonce_bytes);
let nonce_ext = Extension {
oid: oid::ID_PKIX_OCSP_NONCE.to_vec(),
critical: false,
value: inner,
}
.to_der();
let exts_seq = encode_sequence(&nonce_ext);
tbs.extend_from_slice(&encode_context(2, &exts_seq));
}
let tbs_request = encode_sequence(&tbs);
let der = encode_sequence(&tbs_request);
Ok(OcspRequest {
der,
nonce: self.nonce,
})
}
}
impl OcspRequest {
pub fn to_der(&self) -> &[u8] {
&self.der
}
pub fn nonce(&self) -> Option<&[u8]> {
self.nonce.as_deref()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ec::Ed25519PrivateKey;
use crate::rsa::BoxedRsaPrivateKey;
use crate::x509::{
CertSigner, Certificate, DistinguishedName, Extension, Time, Validity, extension,
};
use alloc::vec;
fn rsa_a() -> BoxedRsaPrivateKey {
BoxedRsaPrivateKey::from_pkcs1_pem(include_str!("../../testdata/rsa2048_test_a.pem"))
.expect("rsa key A")
}
fn rsa_b() -> BoxedRsaPrivateKey {
BoxedRsaPrivateKey::from_pkcs1_pem(include_str!("../../testdata/rsa2048_test_b.pem"))
.expect("rsa key B")
}
fn validity() -> Validity {
Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
)
}
fn ed25519() -> Ed25519PrivateKey {
let seed = [7u8; 32];
Ed25519PrivateKey::from_bytes(seed)
}
fn issuer_and_leaf() -> (Certificate, Certificate, BoxedRsaPrivateKey) {
let issuer_key = rsa_a();
let issuer_dn = DistinguishedName::common_name("OCSP test root");
let issuer = Certificate::self_signed_general(
&CertSigner::Rsa(&issuer_key),
&issuer_dn,
&validity(),
1,
true,
&[],
)
.expect("self-sign issuer");
let leaf_key = rsa_b();
let leaf_dn = DistinguishedName::common_name("OCSP test leaf");
let signer = CertSigner::Rsa(&issuer_key);
let leaf_extensions = vec![extension::basic_constraints(false, None)];
let leaf = Certificate::issue_with_extensions(
&signer,
&issuer_dn,
&leaf_dn,
&AnyPublicKey::Rsa(leaf_key.public_key()),
&validity(),
42,
&leaf_extensions,
)
.expect("issue leaf");
(issuer, leaf, issuer_key)
}
#[test]
fn good_roundtrip_and_verify() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::good(
&leaf,
&issuer,
Time::utc(2026, 1, 1, 0, 0, 0),
Some(Time::utc(2026, 1, 8, 0, 0, 0)),
)
.unwrap()
.sign(&signer)
.unwrap();
let from_der = OcspResponse::from_der(resp.to_der().to_vec()).unwrap();
assert_eq!(from_der, resp);
let pem = resp.to_pem();
assert!(pem.contains("BEGIN OCSP RESPONSE"));
let from_pem = OcspResponse::from_pem(&pem).unwrap();
assert_eq!(from_pem, resp);
assert_eq!(
resp.response_status().unwrap(),
OcspResponseStatus::Successful
);
let single = resp.find_response_for(&leaf, &issuer).unwrap().unwrap();
assert_eq!(single.status, OcspCertStatus::Good);
assert_eq!(
single.this_update.to_unix(),
Time::utc(2026, 1, 1, 0, 0, 0).to_unix()
);
assert_eq!(
single.next_update.as_ref().map(|t| t.to_unix()),
Some(Time::utc(2026, 1, 8, 0, 0, 0).to_unix())
);
resp.verify_signature_with(&signer.public_key()).unwrap();
}
#[test]
fn issuer_name_hash_is_over_full_name_tlv() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.sign(&signer)
.unwrap();
let single = resp.find_response_for(&leaf, &issuer).unwrap().unwrap();
let full_name_tlv = issuer.subject_der().unwrap();
let expected = sha1(full_name_tlv).to_vec();
assert_eq!(single.issuer_name_hash, expected);
let mut r = Reader::new(full_name_tlv);
let content = r.read_tlv(tag::SEQUENCE).unwrap();
assert_ne!(single.issuer_name_hash, sha1(content).to_vec());
assert_eq!(single.status, OcspCertStatus::Good);
}
#[test]
fn revoked_with_reason_roundtrip() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::revoked(
&leaf,
&issuer,
Time::utc(2026, 6, 1, 0, 0, 0),
None,
Time::utc(2026, 5, 1, 0, 0, 0),
Some(CrlReason::KeyCompromise),
)
.unwrap()
.sign(&signer)
.unwrap();
let single = resp.find_response_for(&leaf, &issuer).unwrap().unwrap();
match single.status {
OcspCertStatus::Revoked {
revocation_time,
reason,
} => {
assert_eq!(
revocation_time.to_unix(),
Time::utc(2026, 5, 1, 0, 0, 0).to_unix()
);
assert_eq!(reason, Some(CrlReason::KeyCompromise));
}
other => panic!("expected revoked, got {other:?}"),
}
resp.verify_signature_with(&signer.public_key()).unwrap();
}
#[test]
fn unknown_status_decodes() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::with_status(
&leaf,
&issuer,
Time::utc(2026, 1, 1, 0, 0, 0),
None,
OcspCertStatus::Unknown,
)
.unwrap()
.sign(&signer)
.unwrap();
let single = resp.find_response_for(&leaf, &issuer).unwrap().unwrap();
assert_eq!(single.status, OcspCertStatus::Unknown);
}
#[test]
fn responder_id_by_name() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.responder_id_by_name()
.sign(&signer)
.unwrap();
resp.verify_signature_with(&signer.public_key()).unwrap();
let single = resp.find_response_for(&leaf, &issuer).unwrap().unwrap();
assert_eq!(single.status, OcspCertStatus::Good);
}
#[test]
fn delegated_responder_cert_extraction() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let issuer_signer = CertSigner::Rsa(&issuer_key);
let responder_key = ed25519();
let responder_pub = AnyPublicKey::Ed25519(responder_key.public_key());
let responder_dn = DistinguishedName::common_name("OCSP delegated responder");
let mut responder_extensions = vec![extension::basic_constraints(false, None)];
responder_extensions.push(Extension {
oid: oid::EXT_KEY_USAGE.to_vec(),
critical: false,
value: encode_sequence(&oid_tlv(oid::ID_KP_OCSP_SIGNING)),
});
responder_extensions.push(Extension {
oid: oid::ID_PKIX_OCSP_NOCHECK.to_vec(),
critical: false,
value: encode_null(),
});
let responder_cert = Certificate::issue_with_extensions(
&issuer_signer,
&issuer.subject().unwrap(),
&responder_dn,
&responder_pub,
&validity(),
99,
&responder_extensions,
)
.expect("issue responder");
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.delegated_responder_cert(responder_cert.to_der().to_vec())
.sign(&CertSigner::Ed25519(&responder_key))
.unwrap();
let extracted = resp.delegated_responder_cert().unwrap().unwrap();
assert_eq!(extracted.to_der(), responder_cert.to_der());
resp.verify_signature_with(&extracted.subject_public_key().unwrap())
.unwrap();
assert!(
resp.verify_signature_with(&issuer_signer.public_key())
.is_err()
);
let issuer_pub = issuer_signer.public_key();
extracted.verify_signature_with(&issuer_pub).unwrap();
let ekus = extracted.extended_key_usages().unwrap();
assert!(ekus.iter().any(|o| o.as_slice() == oid::ID_KP_OCSP_SIGNING));
}
fn delegated_good_response(
issuer: &Certificate,
leaf: &Certificate,
issuer_key: &BoxedRsaPrivateKey,
responder_validity: &Validity,
) -> OcspResponse {
let issuer_signer = CertSigner::Rsa(issuer_key);
let responder_key = ed25519();
let responder_pub = AnyPublicKey::Ed25519(responder_key.public_key());
let responder_dn = DistinguishedName::common_name("OCSP delegated responder");
let responder_extensions = vec![
extension::basic_constraints(false, None),
Extension {
oid: oid::EXT_KEY_USAGE.to_vec(),
critical: false,
value: encode_sequence(&oid_tlv(oid::ID_KP_OCSP_SIGNING)),
},
];
let responder_cert = Certificate::issue_with_extensions(
&issuer_signer,
&issuer.subject().unwrap(),
&responder_dn,
&responder_pub,
responder_validity,
99,
&responder_extensions,
)
.expect("issue responder");
OcspResponseBuilder::good(leaf, issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.delegated_responder_cert(responder_cert.to_der().to_vec())
.sign(&CertSigner::Ed25519(&responder_key))
.unwrap()
}
#[test]
fn check_for_cert_rejects_ca_true_delegated_responder() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let issuer_signer = CertSigner::Rsa(&issuer_key);
let responder_key = ed25519();
let responder_pub = AnyPublicKey::Ed25519(responder_key.public_key());
let responder_dn = DistinguishedName::common_name("OCSP CA responder");
let responder_extensions = vec![
extension::basic_constraints(true, None),
Extension {
oid: oid::EXT_KEY_USAGE.to_vec(),
critical: false,
value: encode_sequence(&oid_tlv(oid::ID_KP_OCSP_SIGNING)),
},
];
let responder_cert = Certificate::issue_with_extensions(
&issuer_signer,
&issuer.subject().unwrap(),
&responder_dn,
&responder_pub,
&Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
),
100,
&responder_extensions,
)
.expect("issue responder");
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.delegated_responder_cert(responder_cert.to_der().to_vec())
.sign(&CertSigner::Ed25519(&responder_key))
.unwrap();
let now = Time::utc(2026, 6, 1, 0, 0, 0);
assert!(matches!(
resp.check_for_cert(&leaf, &issuer, Some(&now)),
Err(Error::Verification)
));
let ok = delegated_good_response(
&issuer,
&leaf,
&issuer_key,
&Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
),
);
assert_eq!(
ok.check_for_cert(&leaf, &issuer, Some(&now)).unwrap(),
OcspCertStatus::Good
);
}
#[test]
fn check_for_cert_rejects_unknown_critical_extension_on_responder() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let issuer_signer = CertSigner::Rsa(&issuer_key);
let responder_key = ed25519();
let responder_pub = AnyPublicKey::Ed25519(responder_key.public_key());
let responder_dn = DistinguishedName::common_name("OCSP critical-ext responder");
let responder_extensions = vec![
extension::basic_constraints(false, None),
Extension {
oid: oid::EXT_KEY_USAGE.to_vec(),
critical: false,
value: encode_sequence(&oid_tlv(oid::ID_KP_OCSP_SIGNING)),
},
Extension {
oid: alloc::vec![1, 3, 6, 1, 4, 1, 99999, 1],
critical: true,
value: encode_null(),
},
];
let responder_cert = Certificate::issue_with_extensions(
&issuer_signer,
&issuer.subject().unwrap(),
&responder_dn,
&responder_pub,
&Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
),
101,
&responder_extensions,
)
.expect("issue responder");
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.delegated_responder_cert(responder_cert.to_der().to_vec())
.sign(&CertSigner::Ed25519(&responder_key))
.unwrap();
let now = Time::utc(2026, 6, 1, 0, 0, 0);
assert!(matches!(
resp.check_for_cert(&leaf, &issuer, Some(&now)),
Err(Error::Verification)
));
let responder_extensions = vec![
extension::basic_constraints(false, None),
Extension {
oid: oid::EXT_KEY_USAGE.to_vec(),
critical: false,
value: encode_sequence(&oid_tlv(oid::ID_KP_OCSP_SIGNING)),
},
Extension {
oid: alloc::vec![1, 3, 6, 1, 4, 1, 99999, 1],
critical: false,
value: encode_null(),
},
];
let responder_cert = Certificate::issue_with_extensions(
&issuer_signer,
&issuer.subject().unwrap(),
&responder_dn,
&responder_pub,
&Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
),
102,
&responder_extensions,
)
.expect("issue responder");
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.delegated_responder_cert(responder_cert.to_der().to_vec())
.sign(&CertSigner::Ed25519(&responder_key))
.unwrap();
assert_eq!(
resp.check_for_cert(&leaf, &issuer, Some(&now)).unwrap(),
OcspCertStatus::Good
);
}
#[test]
fn check_for_cert_rejects_expired_delegated_responder() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let expired = Validity::new(
Time::utc(2020, 1, 1, 0, 0, 0),
Time::utc(2021, 1, 1, 0, 0, 0),
);
let resp = delegated_good_response(&issuer, &leaf, &issuer_key, &expired);
let now = Time::utc(2026, 6, 1, 0, 0, 0);
assert!(matches!(
resp.check_for_cert(&leaf, &issuer, Some(&now)),
Err(Error::Verification)
));
let valid = Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
);
let resp = delegated_good_response(&issuer, &leaf, &issuer_key, &valid);
assert_eq!(
resp.check_for_cert(&leaf, &issuer, Some(&now)).unwrap(),
OcspCertStatus::Good
);
let resp = delegated_good_response(&issuer, &leaf, &issuer_key, &expired);
assert_eq!(
resp.check_for_cert(&leaf, &issuer, None).unwrap(),
OcspCertStatus::Good
);
}
#[test]
fn verify_rejects_wrong_key_or_tamper() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.sign(&signer)
.unwrap();
let wrong = AnyPublicKey::Rsa(rsa_b().public_key());
assert!(resp.verify_signature_with(&wrong).is_err());
let mut der = resp.to_der().to_vec();
let idx = der.len() / 2;
der[idx] ^= 0x01;
let tampered = OcspResponse::from_der(der).unwrap();
assert!(
tampered
.verify_signature_with(&signer.public_key())
.is_err()
);
}
#[test]
fn find_response_for_serial_magnitude() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.sign(&signer)
.unwrap();
let single = resp.find_response_for(&leaf, &issuer).unwrap().unwrap();
assert_eq!(single.status, OcspCertStatus::Good);
}
#[test]
fn unknown_response_status_rejected() {
let body = encode_sequence(&encode_tlv(0x0a, &[4]));
let r = OcspResponse::from_der(body).unwrap();
assert!(matches!(
r.response_status(),
Err(crate::x509::Error::Malformed)
));
}
#[test]
fn non_successful_status_has_no_basic_response() {
let body = encode_sequence(&encode_tlv(0x0a, &[3]));
let r = OcspResponse::from_der(body).unwrap();
assert_eq!(r.response_status().unwrap(), OcspResponseStatus::TryLater);
assert!(matches!(r.responses(), Err(crate::x509::Error::Malformed)));
}
#[test]
fn next_update_optional() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.sign(&signer)
.unwrap();
let single = resp.find_response_for(&leaf, &issuer).unwrap().unwrap();
assert!(single.next_update.is_none());
}
#[test]
fn sha256_certid_path() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.hash_algorithm(oid::ID_SHA256)
.sign(&signer)
.unwrap();
let single = resp.find_response_for(&leaf, &issuer).unwrap().unwrap();
assert_eq!(single.hash_alg_oid.as_slice(), oid::ID_SHA256);
assert_eq!(single.issuer_name_hash.len(), 32);
assert_eq!(single.issuer_key_hash.len(), 32);
}
#[test]
fn nonce_round_trips_through_builder_and_response() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let nonce = [0x42u8; 16];
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.nonce(&nonce)
.sign(&signer)
.unwrap();
let got = resp.nonce().unwrap().unwrap();
assert_eq!(got, nonce);
resp.verify_signature_with(&signer.public_key()).unwrap();
}
#[test]
fn response_without_nonce_returns_none() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let resp = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.sign(&signer)
.unwrap();
assert!(resp.nonce().unwrap().is_none());
}
#[test]
fn check_with_nonce_accepts_matching_nonce() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let nonce = b"client-nonce-aaaa";
let now = Time::utc(2026, 1, 2, 0, 0, 0);
let resp = OcspResponseBuilder::good(
&leaf,
&issuer,
Time::utc(2026, 1, 1, 0, 0, 0),
Some(Time::utc(2026, 1, 8, 0, 0, 0)),
)
.unwrap()
.nonce(nonce)
.sign(&signer)
.unwrap();
let status = resp
.check_for_cert_with_nonce(&leaf, &issuer, Some(&now), nonce)
.unwrap();
assert_eq!(status, OcspCertStatus::Good);
}
#[test]
fn check_with_nonce_rejects_missing_nonce() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let now = Time::utc(2026, 1, 2, 0, 0, 0);
let resp = OcspResponseBuilder::good(
&leaf,
&issuer,
Time::utc(2026, 1, 1, 0, 0, 0),
Some(Time::utc(2026, 1, 8, 0, 0, 0)),
)
.unwrap()
.sign(&signer)
.unwrap();
let result = resp.check_for_cert_with_nonce(&leaf, &issuer, Some(&now), b"expected");
assert!(matches!(result, Err(Error::Verification)));
}
#[test]
fn check_with_nonce_rejects_mismatched_nonce() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let now = Time::utc(2026, 1, 2, 0, 0, 0);
let resp = OcspResponseBuilder::good(
&leaf,
&issuer,
Time::utc(2026, 1, 1, 0, 0, 0),
Some(Time::utc(2026, 1, 8, 0, 0, 0)),
)
.unwrap()
.nonce(b"responder-chose-different")
.sign(&signer)
.unwrap();
let result = resp.check_for_cert_with_nonce(&leaf, &issuer, Some(&now), b"client-asked");
assert!(matches!(result, Err(Error::Verification)));
}
#[test]
fn request_builder_round_trips_nonce() {
let (issuer, leaf, _) = issuer_and_leaf();
let nonce = b"req-nonce-1234567890";
let req = OcspRequestBuilder::new(&leaf, &issuer)
.unwrap()
.nonce(nonce)
.build()
.unwrap();
assert_eq!(req.nonce(), Some(nonce.as_slice()));
let mut r = Reader::new(req.to_der());
let mut outer = r.read_sequence().unwrap();
let mut tbs = outer.read_sequence().unwrap();
let _req_list = tbs.read_element().unwrap();
let exts_wrapper = tbs.read_tlv(tag::context(2)).unwrap();
let mut exts_outer = Reader::new(exts_wrapper);
let mut exts = exts_outer.read_sequence().unwrap();
let mut ext = exts.read_sequence().unwrap();
let id = parse_oid(ext.read_oid().unwrap()).unwrap();
assert_eq!(id.as_slice(), oid::ID_PKIX_OCSP_NONCE);
let value = ext.read_octet_string().unwrap();
let mut nr = Reader::new(value);
let inner = nr.read_octet_string().unwrap();
assert_eq!(inner, nonce);
}
#[test]
fn request_builder_rejects_oversize_nonce() {
let (issuer, leaf, _) = issuer_and_leaf();
let too_long = [0u8; 33];
let r = OcspRequestBuilder::new(&leaf, &issuer)
.unwrap()
.nonce(&too_long)
.build();
assert!(matches!(r, Err(Error::Malformed)));
}
#[test]
fn request_builder_rejects_empty_nonce() {
let (issuer, leaf, _) = issuer_and_leaf();
let r = OcspRequestBuilder::new(&leaf, &issuer)
.unwrap()
.nonce(&[])
.build();
assert!(matches!(r, Err(Error::Malformed)));
}
#[test]
fn response_builder_rejects_oversize_nonce() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let too_long = [0u8; 33];
let r = OcspResponseBuilder::good(&leaf, &issuer, Time::utc(2026, 1, 1, 0, 0, 0), None)
.unwrap()
.nonce(&too_long)
.sign(&signer);
assert!(matches!(r, Err(Error::Malformed)));
}
#[test]
fn random_nonce_is_present_and_correct_length() {
let (issuer, leaf, _) = issuer_and_leaf();
let mut rng = crate::rng::HmacDrbg::<crate::hash::Sha256>::new(b"ocsp-nonce", b"n", &[]);
let req = OcspRequestBuilder::new(&leaf, &issuer)
.unwrap()
.random_nonce(&mut rng)
.build()
.unwrap();
let n = req.nonce().unwrap();
assert_eq!(n.len(), 16);
}
#[test]
fn check_for_cert_accepts_fresh_response() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let now = Time::utc(2026, 1, 2, 0, 0, 0);
let resp = OcspResponseBuilder::good(
&leaf,
&issuer,
Time::utc(2026, 1, 1, 0, 0, 0),
Some(Time::utc(2026, 1, 8, 0, 0, 0)),
)
.unwrap()
.sign(&signer)
.unwrap();
assert_eq!(
resp.check_for_cert(&leaf, &issuer, Some(&now)).unwrap(),
OcspCertStatus::Good
);
}
#[test]
fn check_for_cert_fails_closed_on_malformed_this_update() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let now = Time::utc(2026, 1, 2, 0, 0, 0);
let resp = OcspResponseBuilder::good(
&leaf,
&issuer,
Time::from_repr("not-a-time"),
Some(Time::utc(2026, 1, 8, 0, 0, 0)),
)
.unwrap()
.sign(&signer)
.unwrap();
assert!(matches!(
resp.check_for_cert(&leaf, &issuer, Some(&now)),
Err(Error::Malformed)
));
}
#[test]
fn check_for_cert_rejects_staple_signature_outside_policy() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let now = Time::utc(2026, 1, 2, 0, 0, 0);
let resp = OcspResponseBuilder::good(
&leaf,
&issuer,
Time::utc(2026, 1, 1, 0, 0, 0),
Some(Time::utc(2026, 1, 8, 0, 0, 0)),
)
.unwrap()
.sign(&signer)
.unwrap();
let none = SignaturePolicy::empty();
assert!(matches!(
resp.check_for_cert_with_options(
&leaf,
&issuer,
&OcspCheckOptions::new(&none).with_time(Some(&now))
),
Err(Error::Verification)
));
assert!(matches!(
resp.verify_signature_with_policy(&issuer.subject_public_key().unwrap(), &none),
Err(Error::Verification)
));
resp.verify_signature_with(&issuer.subject_public_key().unwrap())
.unwrap();
assert_eq!(
resp.check_for_cert(&leaf, &issuer, Some(&now)).unwrap(),
OcspCertStatus::Good
);
}
#[test]
fn check_for_cert_fails_closed_on_malformed_next_update() {
let (issuer, leaf, issuer_key) = issuer_and_leaf();
let signer = CertSigner::Rsa(&issuer_key);
let now = Time::utc(2026, 1, 2, 0, 0, 0);
let resp = OcspResponseBuilder::good(
&leaf,
&issuer,
Time::utc(2026, 1, 1, 0, 0, 0),
Some(Time::from_repr("not-a-time")),
)
.unwrap()
.sign(&signer)
.unwrap();
assert!(matches!(
resp.check_for_cert(&leaf, &issuer, Some(&now)),
Err(Error::Malformed)
));
}
}