use alloc::string::String;
use alloc::vec::Vec;
use super::{AnyPublicKey, CertSigner, Certificate, CrlReason, Error, 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};
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]>,
}
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))?;
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()?;
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 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()?;
let (t, value) = td.read_any()?;
if t != tag::GENERALIZED_TIME {
return Err(Error::Malformed);
}
let s = core::str::from_utf8(value).map_err(|_| Error::Malformed)?;
Ok(Time::from_repr(s))
}
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 check_for_cert(
&self,
leaf: &Certificate,
issuer: &Certificate,
now: Option<&Time>,
) -> Result<OcspCertStatus, Error> {
let issuer_key = issuer.subject_public_key()?;
if self.verify_signature_with(&issuer_key).is_err() {
let responder = self
.delegated_responder_cert()?
.ok_or(Error::Verification)?;
responder.verify_signature_with(&issuer_key)?;
let ekus = responder.extended_key_usages()?;
if !ekus.iter().any(|o| o.as_slice() == oid::ID_KP_OCSP_SIGNING) {
return Err(Error::Verification);
}
let responder_key = responder.subject_public_key()?;
self.verify_signature_with(&responder_key)?;
}
let single = self
.find_response_for(leaf, issuer)?
.ok_or(Error::Malformed)?;
if let Some(now) = now {
let now_u = now.to_unix();
if single.this_update.to_unix() > now_u {
return Err(Error::Malformed);
}
if let Some(nu) = &single.next_update
&& now_u >= nu.to_unix()
{
return Err(Error::Malformed);
}
}
Ok(single.status)
}
pub fn find_response_for(
&self,
leaf: &Certificate,
issuer: &Certificate,
) -> Result<Option<OcspSingleResponse>, Error> {
let issuer_name_value = read_name_value(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_value, 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_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 (t, value) = ri.read_any()?;
if t != tag::GENERALIZED_TIME {
return Err(Error::Malformed);
}
let revocation_time =
Time::from_repr(core::str::from_utf8(value).map_err(|_| Error::Malformed)?);
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()?;
}
OcspCertStatus::Revoked {
revocation_time,
reason,
}
}
0x82 => {
if !status_body.is_empty() {
return Err(Error::Malformed);
}
OcspCertStatus::Unknown
}
_ => return Err(Error::Malformed),
};
let (t, value) = s.read_any()?;
if t != tag::GENERALIZED_TIME {
return Err(Error::Malformed);
}
let this_update = Time::from_repr(core::str::from_utf8(value).map_err(|_| Error::Malformed)?);
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);
let (t, value) = nr.read_any()?;
if t != tag::GENERALIZED_TIME {
return Err(Error::Malformed);
}
next_update = Some(Time::from_repr(
core::str::from_utf8(value).map_err(|_| Error::Malformed)?,
));
nr.finish()?;
}
Ok(OcspSingleResponse {
hash_alg_oid,
issuer_name_hash,
issuer_key_hash,
serial,
status,
this_update,
next_update,
})
}
fn read_name_value(name_tlv: &[u8]) -> Result<&[u8], Error> {
let mut r = Reader::new(name_tlv);
let body = r.read_tlv(tag::SEQUENCE)?;
r.finish()?;
Ok(body)
}
fn hash_pair(
hash_alg_oid: &[u64],
name_value: &[u8],
key_bits: &[u8],
) -> Option<(Vec<u8>, Vec<u8>)> {
if hash_alg_oid == oid::ID_SHA1 {
Some((sha1(name_value).to_vec(), sha1(key_bits).to_vec()))
} else if hash_alg_oid == oid::ID_SHA256 {
Some((sha256(name_value).to_vec(), sha256(key_bits).to_vec()))
} else if hash_alg_oid == oid::ID_SHA384 {
Some((sha384(name_value).to_vec(), sha384(key_bits).to_vec()))
} else if hash_alg_oid == oid::ID_SHA512 {
Some((sha512(name_value).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 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_value: 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>>,
}
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_value: read_name_value(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,
})
}
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 sign(self, signer: &CertSigner<'_>) -> Result<OcspResponse, Error> {
let (name_hash, key_hash) = hash_pair(
self.hash_alg_oid,
&self.issuer_name_value,
&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 {
let name_tlv = encode_sequence(&self.issuer_name_value);
encode_context(1, &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);
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),
})
}
}
#[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 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));
}
#[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);
}
}