use crate::bio::MemBio;
use crate::error::ErrorStack;
use native_ossl_sys as sys;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OcspResponseStatus {
Successful,
MalformedRequest,
InternalError,
TryLater,
SigRequired,
Unauthorized,
Unknown(i32),
}
impl From<i32> for OcspResponseStatus {
fn from(v: i32) -> Self {
match v {
0 => Self::Successful,
1 => Self::MalformedRequest,
2 => Self::InternalError,
3 => Self::TryLater,
5 => Self::SigRequired,
6 => Self::Unauthorized,
n => Self::Unknown(n),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OcspCertStatus {
Good,
Revoked { reason: i32 },
Unknown,
}
impl OcspCertStatus {
fn from_raw(status: i32, reason: i32) -> Self {
match status {
0 => Self::Good,
1 => Self::Revoked { reason },
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone)]
pub struct OcspSingleStatus {
pub cert_status: OcspCertStatus,
pub this_update: Option<String>,
pub next_update: Option<String>,
pub revocation_time: Option<String>,
}
pub struct OcspCertId {
ptr: *mut sys::OCSP_CERTID,
}
unsafe impl Send for OcspCertId {}
impl Clone for OcspCertId {
fn clone(&self) -> Self {
let ptr = unsafe { sys::OCSP_CERTID_dup(self.ptr) };
assert!(!ptr.is_null(), "OCSP_CERTID_dup: allocation failure");
OcspCertId { ptr }
}
}
impl Drop for OcspCertId {
fn drop(&mut self) {
unsafe { sys::OCSP_CERTID_free(self.ptr) };
}
}
impl OcspCertId {
pub fn from_cert(
digest: Option<&crate::digest::DigestAlg>,
subject: &crate::x509::X509,
issuer: &crate::x509::X509,
) -> Result<Self, ErrorStack> {
let dgst_ptr = digest.map_or(std::ptr::null(), crate::digest::DigestAlg::as_ptr);
let ptr = unsafe { sys::OCSP_cert_to_id(dgst_ptr, subject.as_ptr(), issuer.as_ptr()) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(OcspCertId { ptr })
}
}
pub struct OcspRequest {
ptr: *mut sys::OCSP_REQUEST,
}
unsafe impl Send for OcspRequest {}
impl Drop for OcspRequest {
fn drop(&mut self) {
unsafe { sys::OCSP_REQUEST_free(self.ptr) };
}
}
impl OcspRequest {
pub fn new() -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::OCSP_REQUEST_new() };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(OcspRequest { ptr })
}
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
let mut ptr = std::ptr::null_mut::<sys::OCSP_REQUEST>();
let mut p = der.as_ptr();
let len = i64::try_from(der.len()).unwrap_or(i64::MAX);
let result = unsafe {
sys::d2i_OCSP_REQUEST(std::ptr::addr_of_mut!(ptr), std::ptr::addr_of_mut!(p), len)
};
if result.is_null() {
return Err(ErrorStack::drain());
}
Ok(OcspRequest { ptr })
}
pub fn add_cert_id(&mut self, cert_id: OcspCertId) -> Result<(), ErrorStack> {
let rc = unsafe { sys::OCSP_request_add0_id(self.ptr, cert_id.ptr) };
if rc.is_null() {
return Err(ErrorStack::drain());
}
std::mem::forget(cert_id);
Ok(())
}
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
let len = unsafe { sys::i2d_OCSP_REQUEST(self.ptr, std::ptr::null_mut()) };
if len < 0 {
return Err(ErrorStack::drain());
}
#[allow(clippy::cast_sign_loss)] let mut buf = vec![0u8; len as usize];
let mut out_ptr = buf.as_mut_ptr();
let written = unsafe { sys::i2d_OCSP_REQUEST(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
if written < 0 {
return Err(ErrorStack::drain());
}
#[allow(clippy::cast_sign_loss)] buf.truncate(written as usize);
Ok(buf)
}
}
pub struct OcspBasicResp {
ptr: *mut sys::OCSP_BASICRESP,
}
unsafe impl Send for OcspBasicResp {}
impl Drop for OcspBasicResp {
fn drop(&mut self) {
unsafe { sys::OCSP_BASICRESP_free(self.ptr) };
}
}
impl OcspBasicResp {
pub fn verify(&self, store: &crate::x509::X509Store, flags: u64) -> Result<bool, ErrorStack> {
match unsafe {
sys::OCSP_basic_verify(self.ptr, std::ptr::null_mut(), store.as_ptr(), flags)
} {
1 => Ok(true),
0 => Ok(false),
_ => Err(ErrorStack::drain()),
}
}
#[must_use]
pub fn count(&self) -> usize {
let n = unsafe { sys::OCSP_resp_count(self.ptr) };
usize::try_from(n).unwrap_or(0)
}
pub fn find_status(
&self,
cert_id: &OcspCertId,
) -> Result<Option<OcspSingleStatus>, ErrorStack> {
let mut status: i32 = -1;
let mut reason: i32 = -1;
let mut revtime: *mut sys::ASN1_GENERALIZEDTIME = std::ptr::null_mut();
let mut thisupd: *mut sys::ASN1_GENERALIZEDTIME = std::ptr::null_mut();
let mut nextupd: *mut sys::ASN1_GENERALIZEDTIME = std::ptr::null_mut();
let rc = unsafe {
sys::OCSP_resp_find_status(
self.ptr,
cert_id.ptr,
std::ptr::addr_of_mut!(status),
std::ptr::addr_of_mut!(reason),
std::ptr::addr_of_mut!(revtime),
std::ptr::addr_of_mut!(thisupd),
std::ptr::addr_of_mut!(nextupd),
)
};
match rc {
1 => Ok(Some(OcspSingleStatus {
cert_status: OcspCertStatus::from_raw(status, reason),
this_update: generalizedtime_to_str(thisupd),
next_update: generalizedtime_to_str(nextupd),
revocation_time: generalizedtime_to_str(revtime),
})),
0 => Ok(None),
_ => Err(ErrorStack::drain()),
}
}
pub fn check_validity(
&self,
cert_id: &OcspCertId,
sec: i64,
maxsec: i64,
) -> Result<bool, ErrorStack> {
let mut thisupd: *mut sys::ASN1_GENERALIZEDTIME = std::ptr::null_mut();
let mut nextupd: *mut sys::ASN1_GENERALIZEDTIME = std::ptr::null_mut();
let rc = unsafe {
sys::OCSP_resp_find_status(
self.ptr,
cert_id.ptr,
std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::addr_of_mut!(thisupd),
std::ptr::addr_of_mut!(nextupd),
)
};
match rc {
1 => {}
0 => return Ok(false),
_ => return Err(ErrorStack::drain()),
}
match unsafe { sys::OCSP_check_validity(thisupd, nextupd, sec, maxsec) } {
1 => Ok(true),
0 => Ok(false),
_ => Err(ErrorStack::drain()),
}
}
}
pub struct OcspResponse {
ptr: *mut sys::OCSP_RESPONSE,
}
unsafe impl Send for OcspResponse {}
impl Drop for OcspResponse {
fn drop(&mut self) {
unsafe { sys::OCSP_RESPONSE_free(self.ptr) };
}
}
impl OcspResponse {
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
let mut ptr = std::ptr::null_mut::<sys::OCSP_RESPONSE>();
let mut p = der.as_ptr();
let len = i64::try_from(der.len()).unwrap_or(i64::MAX);
let result = unsafe {
sys::d2i_OCSP_RESPONSE(std::ptr::addr_of_mut!(ptr), std::ptr::addr_of_mut!(p), len)
};
if result.is_null() {
return Err(ErrorStack::drain());
}
Ok(OcspResponse { ptr })
}
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
let len = unsafe { sys::i2d_OCSP_RESPONSE(self.ptr, std::ptr::null_mut()) };
if len < 0 {
return Err(ErrorStack::drain());
}
#[allow(clippy::cast_sign_loss)] let mut buf = vec![0u8; len as usize];
let mut out_ptr = buf.as_mut_ptr();
let written = unsafe { sys::i2d_OCSP_RESPONSE(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
if written < 0 {
return Err(ErrorStack::drain());
}
#[allow(clippy::cast_sign_loss)] buf.truncate(written as usize);
Ok(buf)
}
#[must_use]
pub fn status(&self) -> OcspResponseStatus {
OcspResponseStatus::from(unsafe { sys::OCSP_response_status(self.ptr) })
}
pub fn basic(&self) -> Result<OcspBasicResp, ErrorStack> {
let ptr = unsafe { sys::OCSP_response_get1_basic(self.ptr) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(OcspBasicResp { ptr })
}
pub fn verified_status(
&self,
store: &crate::x509::X509Store,
cert_id: &OcspCertId,
) -> Result<Option<OcspSingleStatus>, ErrorStack> {
let basic = self.basic()?;
if !basic.verify(store, 0)? {
return Err(ErrorStack::drain());
}
basic.find_status(cert_id)
}
#[cfg(test)]
fn new_successful_der() -> Vec<u8> {
vec![0x30, 0x03, 0x0A, 0x01, 0x00]
}
}
fn generalizedtime_to_str(t: *mut sys::ASN1_GENERALIZEDTIME) -> Option<String> {
if t.is_null() {
return None;
}
let Ok(mut bio) = MemBio::new() else {
unsafe { sys::ERR_clear_error() };
return None;
};
let rc = unsafe { sys::ASN1_TIME_print(bio.as_ptr(), t.cast::<sys::ASN1_TIME>()) };
if rc != 1 {
unsafe { sys::ERR_clear_error() };
return None;
}
String::from_utf8(bio.into_vec()).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pkey::{KeygenCtx, Pkey, Private, Public};
use crate::x509::{X509Builder, X509NameOwned};
fn make_ca_and_ee() -> (
crate::x509::X509,
Pkey<Private>,
crate::x509::X509,
Pkey<Private>,
) {
let mut ca_kgen = KeygenCtx::new(c"ED25519").unwrap();
let ca_priv = ca_kgen.generate().unwrap();
let ca_pub = Pkey::<Public>::from(ca_priv.clone());
let mut ca_name = X509NameOwned::new().unwrap();
ca_name.add_entry_by_txt(c"CN", b"OCSP Test CA").unwrap();
let ca_cert = X509Builder::new()
.unwrap()
.set_version(2)
.unwrap()
.set_serial_number(1)
.unwrap()
.set_not_before_offset(0)
.unwrap()
.set_not_after_offset(365 * 86400)
.unwrap()
.set_subject_name(&ca_name)
.unwrap()
.set_issuer_name(&ca_name)
.unwrap()
.set_public_key(&ca_pub)
.unwrap()
.sign(&ca_priv, None)
.unwrap()
.build();
let mut ee_kgen = KeygenCtx::new(c"ED25519").unwrap();
let ee_priv = ee_kgen.generate().unwrap();
let ee_pub = Pkey::<Public>::from(ee_priv.clone());
let mut ee_name = X509NameOwned::new().unwrap();
ee_name.add_entry_by_txt(c"CN", b"OCSP Test EE").unwrap();
let ee_cert = X509Builder::new()
.unwrap()
.set_version(2)
.unwrap()
.set_serial_number(2)
.unwrap()
.set_not_before_offset(0)
.unwrap()
.set_not_after_offset(365 * 86400)
.unwrap()
.set_subject_name(&ee_name)
.unwrap()
.set_issuer_name(&ca_name)
.unwrap()
.set_public_key(&ee_pub)
.unwrap()
.sign(&ca_priv, None)
.unwrap()
.build();
(ca_cert, ca_priv, ee_cert, ee_priv)
}
#[test]
fn cert_id_from_cert() {
let (ca_cert, _, ee_cert, _) = make_ca_and_ee();
let id = OcspCertId::from_cert(None, &ee_cert, &ca_cert).unwrap();
let _id2 = id.clone();
}
#[test]
fn ocsp_request_new_and_to_der() {
let req = OcspRequest::new().unwrap();
let der = req.to_der().unwrap();
assert!(!der.is_empty());
}
#[test]
fn ocsp_request_with_cert_id() {
let (ca_cert, _, ee_cert, _) = make_ca_and_ee();
let id = OcspCertId::from_cert(None, &ee_cert, &ca_cert).unwrap();
let mut req = OcspRequest::new().unwrap();
req.add_cert_id(id).unwrap();
let der = req.to_der().unwrap();
assert!(!der.is_empty());
let empty_der = OcspRequest::new().unwrap().to_der().unwrap();
assert!(der.len() > empty_der.len());
}
#[test]
fn ocsp_request_der_roundtrip() {
let req = OcspRequest::new().unwrap();
let der = req.to_der().unwrap();
let req2 = OcspRequest::from_der(&der).unwrap();
assert_eq!(req2.to_der().unwrap(), der);
}
#[test]
fn ocsp_response_status_decode() {
let der = OcspResponse::new_successful_der();
let resp = OcspResponse::from_der(&der).unwrap();
assert_eq!(resp.status(), OcspResponseStatus::Successful);
}
#[test]
fn ocsp_response_der_roundtrip() {
let der = OcspResponse::new_successful_der();
let resp = OcspResponse::from_der(&der).unwrap();
assert_eq!(resp.to_der().unwrap(), der);
}
#[test]
fn ocsp_response_basic_fails_without_body() {
let der = OcspResponse::new_successful_der();
let resp = OcspResponse::from_der(&der).unwrap();
assert!(resp.basic().is_err());
}
}