use crate::{certificate::Certificate, error::AdesError};
use x509_cert::Certificate as X509Certificate;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OcspStatus {
Good,
Revoked,
Unknown,
}
#[cfg(feature = "ocsp")]
pub struct OcspClient {
url_override: Option<String>,
}
#[cfg(feature = "ocsp")]
impl OcspClient {
#[must_use]
pub fn new() -> Self {
Self { url_override: None }
}
#[must_use]
pub fn with_url(url: &str) -> Self {
Self {
url_override: Some(url.to_owned()),
}
}
pub fn check(&self, cert: &Certificate, issuer: &Certificate) -> Result<OcspStatus, AdesError> {
let url = match &self.url_override {
Some(u) => u.clone(),
None => extract_ocsp_url(cert.inner())
.ok_or_else(|| AdesError::Ocsp("no OCSP URL in certificate AIA".to_owned()))?,
};
let req_der = build_ocsp_req(cert.inner(), issuer.inner())?;
let resp_der = self.post(&url, &req_der)?;
parse_ocsp_resp(&resp_der)
}
pub fn raw_response(
&self,
cert: &Certificate,
issuer: &Certificate,
) -> Result<Vec<u8>, AdesError> {
let url = match &self.url_override {
Some(u) => u.clone(),
None => extract_ocsp_url(cert.inner())
.ok_or_else(|| AdesError::Ocsp("no OCSP URL in certificate AIA".to_owned()))?,
};
let req_der = build_ocsp_req(cert.inner(), issuer.inner())?;
self.post(&url, &req_der)
}
fn post(&self, url: &str, req_der: &[u8]) -> Result<Vec<u8>, AdesError> {
let resp = ureq::post(url)
.set("Content-Type", "application/ocsp-request")
.send_bytes(req_der)
.map_err(|e| AdesError::Ocsp(e.to_string()))?;
let mut body = Vec::new();
resp.into_reader()
.read_to_end(&mut body)
.map_err(|e| AdesError::Ocsp(e.to_string()))?;
Ok(body)
}
}
#[cfg(feature = "ocsp")]
impl Default for OcspClient {
fn default() -> Self {
Self::new()
}
}
fn extract_ocsp_url(cert: &X509Certificate) -> Option<String> {
use der::Decode;
use x509_cert::ext::pkix::{name::GeneralName, AccessDescription, AuthorityInfoAccessSyntax};
const ID_AD_OCSP: &str = "1.3.6.1.5.5.7.48.1";
let exts = cert.tbs_certificate.extensions.as_ref()?;
for ext in exts.iter() {
if ext.extn_id.to_string() != "1.3.6.1.5.5.7.1.1" {
continue;
}
let aia = AuthorityInfoAccessSyntax::from_der(ext.extn_value.as_bytes()).ok()?;
for AccessDescription {
access_method,
access_location,
} in aia.0.iter()
{
if access_method.to_string() == ID_AD_OCSP {
if let GeneralName::UniformResourceIdentifier(uri) = access_location {
return Some(uri.to_string());
}
}
}
}
None
}
fn build_ocsp_req(cert: &X509Certificate, issuer: &X509Certificate) -> Result<Vec<u8>, AdesError> {
use der::Encode;
use sha1::Digest as _;
let issuer_name_der = issuer
.tbs_certificate
.subject
.to_der()
.map_err(|e| AdesError::Ocsp(e.to_string()))?;
let issuer_name_hash = sha1::Sha1::digest(&issuer_name_der);
let issuer_spki_der = issuer
.tbs_certificate
.subject_public_key_info
.subject_public_key
.raw_bytes();
let issuer_key_hash = sha1::Sha1::digest(issuer_spki_der);
let serial_der = cert
.tbs_certificate
.serial_number
.to_der()
.map_err(|e| AdesError::Ocsp(e.to_string()))?;
let sha1_oid_bytes = [
0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, ];
let hash_alg = der_tlv(0x30, &sha1_oid_bytes);
let name_hash_os = der_tlv(0x04, &issuer_name_hash);
let key_hash_os = der_tlv(0x04, &issuer_key_hash);
let cert_id = der_tlv(
0x30,
&[
hash_alg.as_slice(),
&name_hash_os,
&key_hash_os,
&serial_der, ]
.concat(),
);
let request = der_tlv(0x30, &cert_id);
let request_list = der_tlv(0x30, &request);
let tbs_request = der_tlv(0x30, &request_list);
Ok(der_tlv(0x30, &tbs_request))
}
fn parse_ocsp_resp(resp_der: &[u8]) -> Result<OcspStatus, AdesError> {
let outer = der_unwrap_seq(resp_der)
.ok_or_else(|| AdesError::Ocsp("invalid OCSPResponse: not a SEQUENCE".to_owned()))?;
let (status_tlv, rest) =
der_next_tlv(outer).ok_or_else(|| AdesError::Ocsp("missing responseStatus".to_owned()))?;
let response_status = *status_tlv
.last()
.ok_or_else(|| AdesError::Ocsp("empty responseStatus".to_owned()))?;
if response_status != 0 {
return Err(AdesError::Ocsp(format!(
"OCSP responder returned status {response_status}"
)));
}
if rest.is_empty() || rest[0] != 0xa0 {
return Err(AdesError::Ocsp(
"OCSPResponse missing responseBytes".to_owned(),
));
}
let (resp_bytes_ctx, _) =
der_next_tlv(rest).ok_or_else(|| AdesError::Ocsp("invalid responseBytes".to_owned()))?;
let resp_bytes_inner = der_strip_explicit_tag(resp_bytes_ctx)
.ok_or_else(|| AdesError::Ocsp("cannot unwrap responseBytes [0]".to_owned()))?;
let (resp_bytes_seq, _) = der_next_tlv(resp_bytes_inner)
.ok_or_else(|| AdesError::Ocsp("invalid ResponseBytes SEQUENCE".to_owned()))?;
let resp_bytes_contents = der_unwrap_seq(resp_bytes_seq)
.ok_or_else(|| AdesError::Ocsp("ResponseBytes not a SEQUENCE".to_owned()))?;
let (_, after_oid) = der_next_tlv(resp_bytes_contents)
.ok_or_else(|| AdesError::Ocsp("missing responseType OID".to_owned()))?;
let (resp_octet_tlv, _) = der_next_tlv(after_oid)
.ok_or_else(|| AdesError::Ocsp("missing response OCTET STRING".to_owned()))?;
let basic_resp_der = der_octet_string_value(resp_octet_tlv)
.ok_or_else(|| AdesError::Ocsp("response is not an OCTET STRING".to_owned()))?;
let basic_contents = der_unwrap_seq(basic_resp_der)
.ok_or_else(|| AdesError::Ocsp("BasicOCSPResponse not a SEQUENCE".to_owned()))?;
let (tbs_data_tlv, _) = der_next_tlv(basic_contents)
.ok_or_else(|| AdesError::Ocsp("missing tbsResponseData".to_owned()))?;
let tbs_contents = der_unwrap_seq(tbs_data_tlv)
.ok_or_else(|| AdesError::Ocsp("tbsResponseData not a SEQUENCE".to_owned()))?;
let responses_seq = find_responses_seq(tbs_contents)
.ok_or_else(|| AdesError::Ocsp("missing responses in ResponseData".to_owned()))?;
let responses_contents = der_unwrap_seq(responses_seq)
.ok_or_else(|| AdesError::Ocsp("responses not a SEQUENCE".to_owned()))?;
let (single_resp_tlv, _) = der_next_tlv(responses_contents)
.ok_or_else(|| AdesError::Ocsp("empty responses list".to_owned()))?;
let single_contents = der_unwrap_seq(single_resp_tlv)
.ok_or_else(|| AdesError::Ocsp("SingleResponse not a SEQUENCE".to_owned()))?;
let (_, after_cert_id) = der_next_tlv(single_contents)
.ok_or_else(|| AdesError::Ocsp("missing certID in SingleResponse".to_owned()))?;
let (cert_status_tlv, _) = der_next_tlv(after_cert_id)
.ok_or_else(|| AdesError::Ocsp("missing certStatus in SingleResponse".to_owned()))?;
let status_tag = cert_status_tlv
.first()
.copied()
.ok_or_else(|| AdesError::Ocsp("empty certStatus".to_owned()))?;
Ok(match status_tag {
0x80 => OcspStatus::Good,
0xa1 => OcspStatus::Revoked,
0x82 => OcspStatus::Unknown,
other => {
return Err(AdesError::Ocsp(format!(
"unknown certStatus tag 0x{other:02x}"
)))
}
})
}
fn find_responses_seq(data: &[u8]) -> Option<&[u8]> {
let mut pos = data;
while !pos.is_empty() {
let tag = *pos.first()?;
let (tlv, rest) = der_next_tlv(pos)?;
match tag {
0x30 => return Some(tlv), _ => pos = rest,
}
}
None
}
fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
let len = value.len();
let mut out = vec![tag];
if len < 128 {
out.push(len as u8);
} else if len < 256 {
out.extend_from_slice(&[0x81, len as u8]);
} else if len < 65536 {
out.extend_from_slice(&[0x82, (len >> 8) as u8, (len & 0xff) as u8]);
} else {
out.extend_from_slice(&[
0x83,
(len >> 16) as u8,
(len >> 8) as u8,
(len & 0xff) as u8,
]);
}
out.extend_from_slice(value);
out
}
fn der_unwrap_seq(data: &[u8]) -> Option<&[u8]> {
if data.first()? != &0x30 {
return None;
}
let (len, contents) = der_decode_len(&data[1..])?;
if contents.len() < len {
return None;
}
Some(&contents[..len])
}
fn der_next_tlv(data: &[u8]) -> Option<(&[u8], &[u8])> {
if data.is_empty() {
return None;
}
let (len, contents) = der_decode_len(&data[1..])?;
let header_size = contents.as_ptr() as usize - data.as_ptr() as usize;
let end = header_size + len;
if end > data.len() {
return None;
}
Some((&data[..end], &data[end..]))
}
fn der_strip_explicit_tag(data: &[u8]) -> Option<&[u8]> {
if data.is_empty() {
return None;
}
let (len, contents) = der_decode_len(&data[1..])?;
if contents.len() < len {
return None;
}
Some(&contents[..len])
}
fn der_octet_string_value(data: &[u8]) -> Option<&[u8]> {
if data.first()? != &0x04 {
return None;
}
let (len, contents) = der_decode_len(&data[1..])?;
if contents.len() < len {
return None;
}
Some(&contents[..len])
}
fn der_decode_len(data: &[u8]) -> Option<(usize, &[u8])> {
let first = *data.first()?;
if first < 0x80 {
return Some((first as usize, &data[1..]));
}
let num_bytes = (first & 0x7f) as usize;
if num_bytes == 0 || num_bytes > 4 || data.len() <= num_bytes {
return None;
}
let mut len = 0usize;
for &b in &data[1..=num_bytes] {
len = (len << 8) | b as usize;
}
Some((len, &data[1 + num_bytes..]))
}