use const_oid::db::rfc5280::ID_AD_OCSP;
use crate::chain_verifier::ChainVerificationFailureReason::InvalidCertificate;
use crate::chain_verifier::{ChainVerificationFailureReason, ChainVerifier, ChainVerifierError};
use x509_cert::Certificate;
use x509_ocsp::builder::OcspRequestBuilder;
use x509_ocsp::Version;
#[derive(Debug)]
enum OcspError {
NetworkError(String),
HttpError(u16),
FetchFailed,
CertificateRevoked,
ValidationError,
}
impl ChainVerifier {
pub fn check_ocsp_status(&self, leaf: &Certificate, issuer: &Certificate) -> Result<(), ChainVerifierError> {
match self.check_ocsp_status_internal(leaf, issuer) {
Ok(()) => Ok(()),
Err(OcspError::NetworkError(_)) | Err(OcspError::HttpError(_)) | Err(OcspError::FetchFailed) => {
Err(ChainVerifierError::VerificationFailure(
ChainVerificationFailureReason::RetryableVerificationFailure,
))
}
Err(OcspError::CertificateRevoked) => {
Err(ChainVerifierError::VerificationFailure(
ChainVerificationFailureReason::CertificateRevoked,
))
}
Err(OcspError::ValidationError) => {
Err(ChainVerifierError::VerificationFailure(InvalidCertificate))
}
}
}
fn check_ocsp_status_internal(&self, leaf: &Certificate, issuer: &Certificate) -> Result<(), OcspError> {
use der::{Decode, Encode};
use x509_ocsp::{BasicOcspResponse, CertStatus, OcspResponse, Request};
use sha1::Sha1;
let ocsp_url = self.extract_ocsp_url(leaf).map_err(|_| OcspError::ValidationError)?;
let request = Request::from_cert::<Sha1>(issuer, leaf)
.map_err(|_| OcspError::ValidationError)?;
let ocsp_request = OcspRequestBuilder::new(Version::V1)
.with_request(request)
.build();
let request_bytes = ocsp_request
.to_der()
.map_err(|_| OcspError::ValidationError)?;
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.map_err(|e| OcspError::NetworkError(format!("Failed to build HTTP client: {}", e)))?;
let response = client
.post(&ocsp_url)
.header("Content-Type", "application/ocsp-request")
.body(request_bytes)
.send()
.map_err(|e| {
OcspError::NetworkError(format!("OCSP request failed: {}", e))
})?;
let status = response.status();
if !status.is_success() {
return Err(OcspError::HttpError(status.as_u16()));
}
let response_bytes = response
.bytes()
.map_err(|_| OcspError::FetchFailed)?;
let ocsp_response = OcspResponse::from_der(&response_bytes)
.map_err(|_| OcspError::ValidationError)?;
use x509_ocsp::OcspResponseStatus;
match ocsp_response.response_status {
OcspResponseStatus::Successful => {}
_ => return Err(OcspError::ValidationError),
}
let response_bytes = ocsp_response
.response_bytes
.ok_or_else(|| OcspError::ValidationError)?;
const ID_PKIX_OCSP_BASIC: &str = "1.3.6.1.5.5.7.48.1.1";
if response_bytes.response_type.to_string() != ID_PKIX_OCSP_BASIC {
return Err(OcspError::ValidationError);
}
let basic_response = BasicOcspResponse::from_der(response_bytes.response.as_bytes())
.map_err(|_| OcspError::ValidationError)?;
for single_response in &basic_response.tbs_response_data.responses {
match &single_response.cert_status {
CertStatus::Good(_) => return Ok(()), CertStatus::Revoked(_) => {
return Err(OcspError::CertificateRevoked);
}
CertStatus::Unknown(_) => {
return Err(OcspError::ValidationError);
}
}
}
Err(OcspError::ValidationError)
}
fn extract_ocsp_url(&self, cert: &Certificate) -> Result<String, ChainVerifierError> {
use const_oid::ObjectIdentifier;
let aia_oid = ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.1.1");
let Some(extensions) = &cert.tbs_certificate.extensions else {
return Err(ChainVerifierError::VerificationFailure(InvalidCertificate));
};
for ext in extensions {
if ext.extn_id == aia_oid {
if let Ok(url) = self.parse_aia_for_ocsp(ext.extn_value.as_bytes()) {
return Ok(url);
}
}
}
Err(ChainVerifierError::VerificationFailure(InvalidCertificate))
}
fn parse_aia_for_ocsp(&self, aia_bytes: &[u8]) -> Result<String, ChainVerifierError> {
use crate::asn1::asn1_basics::{read_sequence, read_oid, read_tlv};
let (mut offset, length) = read_sequence(aia_bytes, 0)
.map_err(|e| ChainVerifierError::InternalX509Error(e.to_string()))?;
let end_offset = offset + length;
while offset < end_offset {
let (desc_offset, desc_length) = read_sequence(aia_bytes, offset)
.map_err(|e| ChainVerifierError::InternalX509Error(e.to_string()))?;
let desc_end = desc_offset + desc_length;
let (oid_offset, oid_length) = read_oid(aia_bytes, desc_offset)
.map_err(|e| ChainVerifierError::InternalX509Error(e.to_string()))?;
let oid_bytes = &aia_bytes[oid_offset..oid_offset + oid_length];
let expected_ocsp_oid = ID_AD_OCSP.as_bytes();
if oid_bytes == expected_ocsp_oid {
let location_offset = oid_offset + oid_length;
let (tag, uri_length, uri_offset) = read_tlv(aia_bytes, location_offset)
.map_err(|e| ChainVerifierError::InternalX509Error(e.to_string()))?;
if tag == 0x86 {
let uri_bytes = &aia_bytes[uri_offset..uri_offset + uri_length];
let uri = std::str::from_utf8(uri_bytes)
.map_err(|_| ChainVerifierError::VerificationFailure(InvalidCertificate))?;
return Ok(uri.to_string());
}
}
offset = desc_end;
}
Err(ChainVerifierError::VerificationFailure(InvalidCertificate))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::x509::x509;
#[test]
fn test_extract_ocsp_url_missing_aia() {
let cert_der = include_bytes!("../tests/resources/certs/testCA.der");
let cert = x509::parse_certificate(cert_der).unwrap();
let verifier = ChainVerifier::new(vec![]);
let result = verifier.extract_ocsp_url(&cert);
assert!(result.is_err());
}
#[test]
fn test_extract_ocsp_url_with_aia() {
let cert_der = include_bytes!("../tests/resources/certs/testCA.der");
let cert = x509::parse_certificate(cert_der).unwrap();
let verifier = ChainVerifier::new(vec![]);
let result = verifier.extract_ocsp_url(&cert);
assert!(result.is_err());
}
#[test]
fn test_ocsp_response_parsing() {
use der::{Decode, Encode};
use x509_ocsp::{OcspResponse, OcspResponseStatus};
let response = OcspResponse {
response_status: OcspResponseStatus::Successful,
response_bytes: None,
};
let encoded = response.to_der().unwrap();
let decoded = OcspResponse::from_der(&encoded).unwrap();
assert_eq!(decoded.response_status, OcspResponseStatus::Successful);
}
#[test]
fn test_cert_id_creation() {
use der::asn1::{ObjectIdentifier, OctetString};
use x509_cert::serial_number::SerialNumber;
use x509_cert::spki::AlgorithmIdentifierOwned;
use x509_ocsp::CertId;
let sha1_oid = ObjectIdentifier::new_unwrap("1.3.14.3.2.26");
let hash_algorithm = AlgorithmIdentifierOwned {
oid: sha1_oid,
parameters: None,
};
let issuer_name_hash = OctetString::new(&[0u8; 20]).unwrap();
let issuer_key_hash = OctetString::new(&[0u8; 20]).unwrap();
let serial = SerialNumber::new(&[1, 2, 3]).unwrap();
let cert_id = CertId {
hash_algorithm,
issuer_name_hash,
issuer_key_hash,
serial_number: serial,
};
assert_eq!(
cert_id
.issuer_name_hash
.as_bytes()
.len(),
20
);
assert_eq!(cert_id.issuer_key_hash.as_bytes().len(), 20);
}
#[test]
fn test_ocsp_request_creation() {
use der::asn1::{ObjectIdentifier, OctetString};
use der::Encode;
use x509_cert::serial_number::SerialNumber;
use x509_cert::spki::AlgorithmIdentifierOwned;
use x509_ocsp::{CertId, OcspRequest, Request, TbsRequest};
let sha1_oid = ObjectIdentifier::new_unwrap("1.3.14.3.2.26");
let hash_algorithm = AlgorithmIdentifierOwned {
oid: sha1_oid,
parameters: None,
};
let cert_id = CertId {
hash_algorithm,
issuer_name_hash: OctetString::new(&[0u8; 20]).unwrap(),
issuer_key_hash: OctetString::new(&[0u8; 20]).unwrap(),
serial_number: SerialNumber::new(&[1]).unwrap(),
};
let request = Request {
req_cert: cert_id,
single_request_extensions: None,
};
let tbs_request = TbsRequest {
version: x509_ocsp::Version::V1,
requestor_name: None,
request_list: vec![request],
request_extensions: None,
};
let ocsp_request = OcspRequest {
tbs_request,
optional_signature: None,
};
let encoded = ocsp_request.to_der();
assert!(encoded.is_ok());
}
}