use crate::c2pa_trust::OcspStatus;
pub const OCSP_CONFLICTING_REVOKED_RESPONSE: &str = "com.encypher.ocsp.conflictingRevokedResponse";
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(super) struct CertificateEvidence {
merged: Option<OcspStatus>,
any_good: bool,
}
impl CertificateEvidence {
pub(super) fn absorb(&mut self, candidate: Option<OcspStatus>) {
self.any_good |= OcspStatus::merge(None, candidate) == Some(OcspStatus::Good);
self.merged = OcspStatus::merge(self.merged, candidate);
}
pub(super) fn resolve(self, any_good_wins: bool) -> (Option<OcspStatus>, bool) {
let outranked = any_good_wins
&& self.any_good
&& matches!(self.merged, Some(OcspStatus::Revoked { .. }));
if outranked {
(Some(OcspStatus::Good), true)
} else {
(self.merged, false)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use time::OffsetDateTime;
fn revoked() -> OcspStatus {
OcspStatus::Revoked {
revocation_time: OffsetDateTime::UNIX_EPOCH,
reason: Some(crate::c2pa_trust::ocsp::OcspRevocationReason::KeyCompromise),
}
}
fn evidence(statuses: impl IntoIterator<Item = Option<OcspStatus>>) -> CertificateEvidence {
let mut evidence = CertificateEvidence::default();
for status in statuses {
evidence.absorb(status);
}
evidence
}
#[test]
fn a_good_response_outranks_a_revoked_one_only_under_the_conformance_posture() {
for pair in [
[Some(revoked()), Some(OcspStatus::Good)],
[Some(OcspStatus::Good), Some(revoked())],
] {
let evidence = evidence(pair);
assert_eq!(
evidence.resolve(false),
(Some(revoked()), false),
"the default posture stays fail-closed"
);
assert_eq!(
evidence.resolve(true),
(Some(OcspStatus::Good), true),
"VAL-STRU-0027: any qualifying good response settles the status"
);
}
}
#[test]
fn a_lone_revoked_response_is_revoked_under_both_postures() {
let evidence = evidence([Some(revoked()), Some(OcspStatus::Unknown), None]);
assert_eq!(evidence.resolve(false), (Some(revoked()), false));
assert_eq!(
evidence.resolve(true),
(Some(revoked()), false),
"with no good response there is nothing to outrank and no conflict to report"
);
}
#[test]
fn a_lone_good_response_reports_no_conflict() {
let evidence = evidence([Some(OcspStatus::Good), Some(OcspStatus::Unknown)]);
for posture in [false, true] {
assert_eq!(evidence.resolve(posture), (Some(OcspStatus::Good), false));
}
}
}