#[cfg(test)]
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OcspStatus {
Good,
Revoked,
Unknown,
Malformed,
}
#[must_use]
pub fn parse_ocsp_status(response_der: &[u8]) -> OcspStatus {
if response_der.is_empty() {
return OcspStatus::Malformed;
}
const MAX_SCAN_BYTES: usize = 65_536;
let scan_limit = response_der.len().min(MAX_SCAN_BYTES);
let mut seen_header = false;
for i in 0..scan_limit {
let b = response_der[i];
if !seen_header {
if b == 0x30 {
seen_header = true;
}
continue;
}
match b {
0x80 if response_der.get(i + 1) == Some(&0x00) => return OcspStatus::Good,
0xA1 | 0x81 => return OcspStatus::Revoked,
0x82 => return OcspStatus::Unknown,
_ => {}
}
}
OcspStatus::Malformed
}
pub fn require_good_status(
response_der: &[u8],
) -> Result<(), zerodds_security::error::SecurityError> {
use zerodds_security::error::{SecurityError, SecurityErrorKind};
match parse_ocsp_status(response_der) {
OcspStatus::Good => Ok(()),
OcspStatus::Revoked => Err(SecurityError::new(
SecurityErrorKind::AuthenticationFailed,
"ocsp: cert ist revoked",
)),
OcspStatus::Unknown => Err(SecurityError::new(
SecurityErrorKind::AuthenticationFailed,
"ocsp: responder kennt cert nicht",
)),
OcspStatus::Malformed => Err(SecurityError::new(
SecurityErrorKind::BadArgument,
"ocsp: response nicht parsbar",
)),
}
}
#[cfg(test)]
#[must_use]
fn build_test_response(cert_status_tag: u8) -> Vec<u8> {
let payload_len = match cert_status_tag {
0x80 => 2, 0xA1 | 0x81 => 1, 0x82 => 1,
_ => 1,
};
let mut out = Vec::with_capacity(2 + payload_len);
out.push(0x30); #[allow(clippy::cast_possible_truncation)]
out.push(payload_len as u8);
out.push(cert_status_tag);
if cert_status_tag == 0x80 {
out.push(0x00); }
out
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn good_status_parses_to_good() {
let r = build_test_response(0x80);
assert_eq!(parse_ocsp_status(&r), OcspStatus::Good);
}
#[test]
fn revoked_tag_a1_parses_to_revoked() {
let r = build_test_response(0xA1);
assert_eq!(parse_ocsp_status(&r), OcspStatus::Revoked);
}
#[test]
fn revoked_tag_81_parses_to_revoked() {
let r = build_test_response(0x81);
assert_eq!(parse_ocsp_status(&r), OcspStatus::Revoked);
}
#[test]
fn unknown_tag_82_parses_to_unknown() {
let r = build_test_response(0x82);
assert_eq!(parse_ocsp_status(&r), OcspStatus::Unknown);
}
#[test]
fn empty_input_is_malformed() {
assert_eq!(parse_ocsp_status(&[]), OcspStatus::Malformed);
}
#[test]
fn require_good_accepts_good() {
let r = build_test_response(0x80);
assert!(require_good_status(&r).is_ok());
}
#[test]
fn require_good_rejects_revoked_with_auth_failed() {
use zerodds_security::error::SecurityErrorKind;
let r = build_test_response(0xA1);
let err = require_good_status(&r).unwrap_err();
assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
}
#[test]
fn require_good_rejects_unknown() {
let r = build_test_response(0x82);
assert!(require_good_status(&r).is_err());
}
#[test]
fn require_good_rejects_malformed() {
use zerodds_security::error::SecurityErrorKind;
let err = require_good_status(&[]).unwrap_err();
assert_eq!(err.kind, SecurityErrorKind::BadArgument);
}
#[test]
fn prefix_bytes_before_sequence_are_skipped() {
let r = [0x80, 0x00, 0x30, 0x02, 0x82, 0x00];
assert_eq!(parse_ocsp_status(&r), OcspStatus::Unknown);
}
#[test]
fn sequence_tag_recognized_via_equality() {
let r = [0x30, 0x80, 0x00];
assert_eq!(parse_ocsp_status(&r), OcspStatus::Good);
}
#[test]
fn good_tag_requires_zero_length() {
let r = [0x30, 0x02, 0x80, 0x42];
assert_ne!(parse_ocsp_status(&r), OcspStatus::Good);
}
}