use synta::{BitStringRef, Decoder, Encoding, Integer, OctetStringRef};
use crate::ocsp_2024_88_types::{OCSPRequest, Request, Signature, TBSRequest};
use crate::{AlgorithmIdentifier, GeneralName};
pub struct CertIDSpec<'a> {
pub hash_algorithm_der: &'a [u8],
pub issuer_name_hash: &'a [u8],
pub issuer_key_hash: &'a [u8],
pub serial: &'a [u8],
}
struct PendingRequest {
hash_algorithm_der: Vec<u8>,
issuer_name_hash: Vec<u8>,
issuer_key_hash: Vec<u8>,
serial: Vec<u8>,
}
fn make_tbs_request<'a>(
requests: &'a [PendingRequest],
requestor_name_der: Option<&'a [u8]>,
) -> Result<TBSRequest<'a>, String> {
let requestor_name = requestor_name_der
.map(|der| {
Decoder::new(der, Encoding::Der)
.decode::<GeneralName<'_>>()
.map_err(|e| format!("re-decode requestorName failed: {e}"))
})
.transpose()?;
let mut request_list: Vec<Request<'_>> = Vec::with_capacity(requests.len());
for r in requests {
let hash_algorithm: AlgorithmIdentifier<'_> =
Decoder::new(&r.hash_algorithm_der, Encoding::Der)
.decode()
.map_err(|e| format!("re-decode hash_algorithm_der failed: {e}"))?;
let cert_id = crate::ocsp_2024_88_types::CertID {
hash_algorithm,
issuer_name_hash: OctetStringRef::new(&r.issuer_name_hash),
issuer_key_hash: OctetStringRef::new(&r.issuer_key_hash),
serial_number: Integer::from_unsigned_bytes(&r.serial),
};
request_list.push(Request {
req_cert: cert_id,
single_request_extensions: None,
});
}
Ok(TBSRequest {
version: None,
requestor_name,
request_list,
request_extensions: None,
})
}
fn encode_tbs_request(
requests: &[PendingRequest],
requestor_name_der: Option<&[u8]>,
) -> Result<Vec<u8>, String> {
make_tbs_request(requests, requestor_name_der)?
.to_der()
.map_err(|e| format!("TBSRequest encode error: {e}"))
}
pub struct OCSPRequestBuilder {
requestor_name_der: Option<Vec<u8>>,
requests: Vec<PendingRequest>,
error: Option<String>,
}
impl Default for OCSPRequestBuilder {
fn default() -> Self {
Self::new()
}
}
impl OCSPRequestBuilder {
pub fn new() -> Self {
Self {
requestor_name_der: None,
requests: Vec::new(),
error: None,
}
}
pub fn requestor_name(mut self, name_der: &[u8]) -> Self {
if self.error.is_some() {
return self;
}
match Decoder::new(name_der, Encoding::Der).decode::<GeneralName<'_>>() {
Ok(_) => self.requestor_name_der = Some(name_der.to_vec()),
Err(e) => self.error = Some(format!("invalid requestorName GeneralName DER: {e}")),
}
self
}
pub fn add_request(mut self, spec: CertIDSpec<'_>) -> Self {
if self.error.is_some() {
return self;
}
if let Err(e) =
Decoder::new(spec.hash_algorithm_der, Encoding::Der).decode::<AlgorithmIdentifier<'_>>()
{
self.error = Some(format!("invalid hash_algorithm_der: {e}"));
return self;
}
self.requests.push(PendingRequest {
hash_algorithm_der: spec.hash_algorithm_der.to_vec(),
issuer_name_hash: spec.issuer_name_hash.to_vec(),
issuer_key_hash: spec.issuer_key_hash.to_vec(),
serial: spec.serial.to_vec(),
});
self
}
fn build_tbs_struct(self) -> Result<(Vec<PendingRequest>, Option<Vec<u8>>), String> {
if let Some(e) = self.error {
return Err(e);
}
if self.requests.is_empty() {
return Err("requestList is empty: add at least one request via add_request()".into());
}
Ok((self.requests, self.requestor_name_der))
}
pub fn build_tbs_inner(self) -> Result<Vec<u8>, String> {
let (requests, requestor_name_der) = self.build_tbs_struct()?;
encode_tbs_request(&requests, requestor_name_der.as_deref())
}
pub fn build_tbs(self) -> Result<Vec<u8>, String> {
let (requests, requestor_name_der) = self.build_tbs_struct()?;
let tbs = make_tbs_request(&requests, requestor_name_der.as_deref())?;
let ocsp_request = OCSPRequest {
tbs_request: tbs,
optional_signature: None,
};
ocsp_request
.to_der()
.map_err(|e| format!("OCSPRequest encode error: {e}"))
}
pub fn assemble(
tbs_der: &[u8],
sig_alg_der: &[u8],
signature: &[u8],
) -> Result<Vec<u8>, String> {
let tbs: TBSRequest<'_> = Decoder::new(tbs_der, Encoding::Der)
.decode()
.map_err(|e| format!("assemble: tbs_der decode error: {e}"))?;
let sig_algorithm: AlgorithmIdentifier<'_> = Decoder::new(sig_alg_der, Encoding::Der)
.decode()
.map_err(|e| format!("assemble: sig_alg_der decode error: {e}"))?;
let sig_bstr = BitStringRef::new(signature, 0)
.map_err(|e| format!("assemble: signature BIT STRING error: {e}"))?;
let ocsp_sig = Signature {
signature_algorithm: sig_algorithm,
signature: sig_bstr,
certs: None,
};
let ocsp_request = OCSPRequest {
tbs_request: tbs,
optional_signature: Some(ocsp_sig),
};
ocsp_request
.to_der()
.map_err(|e| format!("OCSPRequest encode error: {e}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sha1_alg_der() -> &'static [u8] {
&[
0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00,
]
}
fn sha256_rsa_alg_der() -> &'static [u8] {
&[
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05,
0x00,
]
}
#[test]
fn build_unsigned_ocsp_request() {
let name_hash = [0x01u8; 20];
let key_hash = [0x02u8; 20];
let serial = [0x03u8];
let ocsp_der = OCSPRequestBuilder::new()
.add_request(CertIDSpec {
hash_algorithm_der: sha1_alg_der(),
issuer_name_hash: &name_hash,
issuer_key_hash: &key_hash,
serial: &serial,
})
.build_tbs()
.expect("build_tbs should succeed");
assert!(!ocsp_der.is_empty(), "OCSPRequest DER must not be empty");
let decoded: OCSPRequest<'_> = Decoder::new(&ocsp_der, Encoding::Der)
.decode()
.expect("OCSPRequest round-trip decode failed");
assert!(decoded.optional_signature.is_none());
assert_eq!(decoded.tbs_request.request_list.len(), 1);
assert!(decoded.tbs_request.requestor_name.is_none());
assert_eq!(
decoded.tbs_request.request_list[0]
.req_cert
.issuer_name_hash
.as_bytes(),
&name_hash
);
assert_eq!(
decoded.tbs_request.request_list[0]
.req_cert
.issuer_key_hash
.as_bytes(),
&key_hash
);
}
#[test]
fn assemble_signed_request() {
let name_hash = [0xaau8; 20];
let key_hash = [0xbbu8; 20];
let serial = [0x01u8];
let fake_sig = [0xdeu8; 32];
let tbs_inner = OCSPRequestBuilder::new()
.add_request(CertIDSpec {
hash_algorithm_der: sha1_alg_der(),
issuer_name_hash: &name_hash,
issuer_key_hash: &key_hash,
serial: &serial,
})
.build_tbs_inner()
.expect("build_tbs_inner should succeed");
let ocsp_der = OCSPRequestBuilder::assemble(&tbs_inner, sha256_rsa_alg_der(), &fake_sig)
.expect("assemble should succeed");
let decoded: OCSPRequest<'_> = Decoder::new(&ocsp_der, Encoding::Der)
.decode()
.expect("OCSPRequest round-trip decode failed");
assert!(
decoded.optional_signature.is_some(),
"signature must be present"
);
assert_eq!(decoded.tbs_request.request_list.len(), 1);
}
#[test]
fn empty_request_list_returns_error() {
let result = OCSPRequestBuilder::new().build_tbs();
assert!(result.is_err(), "expected error for empty request list");
let msg = result.unwrap_err();
assert!(
msg.contains("requestList"),
"error message must mention 'requestList', got: {msg}"
);
}
#[test]
fn invalid_alg_id_propagates() {
let result = OCSPRequestBuilder::new()
.add_request(CertIDSpec {
hash_algorithm_der: &[0xff, 0x00], issuer_name_hash: &[0x01u8; 20],
issuer_key_hash: &[0x02u8; 20],
serial: &[0x01u8],
})
.build_tbs();
assert!(result.is_err());
}
#[test]
fn multiple_requests_accumulated() {
let name_hash = [0x01u8; 20];
let key_hash = [0x02u8; 20];
let serial1 = [0x01u8];
let serial2 = [0x02u8];
let serial3 = [0x03u8];
let ocsp_der = OCSPRequestBuilder::new()
.add_request(CertIDSpec {
hash_algorithm_der: sha1_alg_der(),
issuer_name_hash: &name_hash,
issuer_key_hash: &key_hash,
serial: &serial1,
})
.add_request(CertIDSpec {
hash_algorithm_der: sha1_alg_der(),
issuer_name_hash: &name_hash,
issuer_key_hash: &key_hash,
serial: &serial2,
})
.add_request(CertIDSpec {
hash_algorithm_der: sha1_alg_der(),
issuer_name_hash: &name_hash,
issuer_key_hash: &key_hash,
serial: &serial3,
})
.build_tbs()
.expect("build_tbs should succeed with three entries");
let decoded: OCSPRequest<'_> = Decoder::new(&ocsp_der, Encoding::Der)
.decode()
.expect("OCSPRequest decode failed");
assert_eq!(decoded.tbs_request.request_list.len(), 3);
}
#[test]
fn invalid_requestor_name_propagates() {
let result = OCSPRequestBuilder::new()
.requestor_name(&[0xff, 0x00]) .add_request(CertIDSpec {
hash_algorithm_der: sha1_alg_der(),
issuer_name_hash: &[0x01u8; 20],
issuer_key_hash: &[0x02u8; 20],
serial: &[0x01u8],
})
.build_tbs();
assert!(
result.is_err(),
"expected error for invalid requestorName DER"
);
let msg = result.unwrap_err();
assert!(
msg.contains("requestorName"),
"error must mention 'requestorName', got: {msg}"
);
}
#[test]
fn assemble_invalid_tbs_returns_error() {
let result = OCSPRequestBuilder::assemble(&[0xff, 0x00], sha256_rsa_alg_der(), &[0u8; 32]);
assert!(result.is_err(), "expected error for garbage tbs_der");
}
#[test]
fn assemble_invalid_sig_alg_returns_error() {
let name_hash = [0xaau8; 20];
let key_hash = [0xbbu8; 20];
let serial = [0x01u8];
let tbs_inner = OCSPRequestBuilder::new()
.add_request(CertIDSpec {
hash_algorithm_der: sha1_alg_der(),
issuer_name_hash: &name_hash,
issuer_key_hash: &key_hash,
serial: &serial,
})
.build_tbs_inner()
.expect("build_tbs_inner should succeed");
let result = OCSPRequestBuilder::assemble(&tbs_inner, &[0xff, 0x00], &[0u8; 32]);
assert!(result.is_err(), "expected error for garbage sig_alg_der");
}
}