use crate::{Error, RevocationChecker};
use der::{Decode as _, Encode as _};
use pkix_path::{names_match, SignatureVerifier, TrustAnchor};
use spki::der::referenced::OwnedToRef as _;
use x509_cert::Certificate;
use x509_ocsp::{BasicOcspResponse, CertStatus, OcspResponse, OcspResponseStatus, ResponderId};
const OID_PKIX_OCSP_BASIC: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.48.1.1");
const OID_SHA1: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("1.3.14.3.2.26");
const OID_SHA256: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
const OID_SHA384: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.2");
const OID_SHA512: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.3");
const OID_EXTENDED_KEY_USAGE: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.5.29.37");
const OID_KP_OCSP_SIGNING: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.3.9");
#[derive(Clone, Debug)]
pub struct OcspChecker<V> {
basic: BasicOcspResponse,
now_unix: u64,
verifier: V,
}
impl<V: SignatureVerifier> OcspChecker<V> {
pub fn new(response_der: impl AsRef<[u8]>, now_unix: u64, verifier: V) -> crate::Result<Self> {
let basic = parse_basic_response(response_der.as_ref())?;
Ok(Self {
basic,
now_unix,
verifier,
})
}
}
impl<V: SignatureVerifier> RevocationChecker for OcspChecker<V> {
fn check_revocation(&self, cert: &Certificate, issuer: &Certificate) -> crate::Result<()> {
if !names_match(
&issuer.tbs_certificate.subject,
&cert.tbs_certificate.issuer,
) {
return Err(Error::OcspIssuerCertMismatch);
}
let basic = &self.basic;
let issuer_subject = &issuer.tbs_certificate.subject;
let issuer_spki_owned = &issuer.tbs_certificate.subject_public_key_info;
let issuer_spki_raw = issuer_spki_owned.subject_public_key.raw_bytes();
let signing_spki = resolve_signing_key_for_response(
basic,
issuer_subject,
issuer_spki_owned.owned_to_ref(),
issuer_spki_raw,
&self.verifier,
produced_at_unix_secs(basic),
)?;
let tbs_bytes = basic
.tbs_response_data
.to_der()
.map_err(|e| Error::OcspParseError(crate::DerError::new(e)))?;
self.verifier
.verify_signature(
basic.signature_algorithm.owned_to_ref(),
signing_spki,
&tbs_bytes,
basic.signature.raw_bytes(),
)
.map_err(|_| Error::OcspSignatureInvalid)?;
let cert_serial = &cert.tbs_certificate.serial_number;
let single = basic
.tbs_response_data
.responses
.iter()
.find(|r| &r.cert_id.serial_number == cert_serial)
.ok_or(Error::OcspStatusUnknown)?;
let hash_oid = &single.cert_id.hash_algorithm.oid;
let name_der = issuer
.tbs_certificate
.subject
.to_der()
.map_err(|e| Error::OcspParseError(crate::DerError::new(e)))?;
let key_raw = issuer
.tbs_certificate
.subject_public_key_info
.subject_public_key
.raw_bytes();
let expected_name_hash = hash_certid_input(hash_oid, &name_der)?;
let expected_key_hash = hash_certid_input(hash_oid, key_raw)?;
if single.cert_id.issuer_name_hash.as_bytes() != expected_name_hash.as_slice()
|| single.cert_id.issuer_key_hash.as_bytes() != expected_key_hash.as_slice()
{
return Err(Error::OcspCertIdMismatch);
}
let produced_at = basic
.tbs_response_data
.produced_at
.as_ref()
.to_unix_duration()
.as_secs();
if self.now_unix < produced_at {
return Err(Error::OcspMalformed);
}
let this_update = single.this_update.as_ref().to_unix_duration().as_secs();
if self.now_unix < this_update {
return Err(Error::OcspExpired);
}
let next_update = single.next_update.as_ref().ok_or(Error::OcspExpired)?;
if self.now_unix > next_update.as_ref().to_unix_duration().as_secs() {
return Err(Error::OcspExpired);
}
match single.cert_status {
CertStatus::Good(_) => Ok(()),
CertStatus::Revoked(ref info) => Err(Error::Revoked {
serial: cert_serial.clone(),
reason_code: info.revocation_reason,
}),
CertStatus::Unknown(_) => Err(Error::OcspStatusUnknown),
}
}
fn check_revocation_against_anchor(
&self,
cert: &Certificate,
anchor: &TrustAnchor,
) -> crate::Result<()> {
if !names_match(&anchor.subject, &cert.tbs_certificate.issuer) {
return Err(Error::OcspIssuerCertMismatch);
}
let basic = &self.basic;
let anchor_subject = &anchor.subject;
let anchor_spki_raw = anchor.subject_public_key_info.subject_public_key.raw_bytes();
let signing_spki = resolve_signing_key_for_response(
basic,
anchor_subject,
anchor.subject_public_key_info.owned_to_ref(),
anchor_spki_raw,
&self.verifier,
produced_at_unix_secs(basic),
)?;
let tbs_bytes = basic
.tbs_response_data
.to_der()
.map_err(|e| Error::OcspParseError(crate::DerError::new(e)))?;
self.verifier
.verify_signature(
basic.signature_algorithm.owned_to_ref(),
signing_spki,
&tbs_bytes,
basic.signature.raw_bytes(),
)
.map_err(|_| Error::OcspSignatureInvalid)?;
let cert_serial = &cert.tbs_certificate.serial_number;
let single = basic
.tbs_response_data
.responses
.iter()
.find(|r| &r.cert_id.serial_number == cert_serial)
.ok_or(Error::OcspStatusUnknown)?;
let hash_oid = &single.cert_id.hash_algorithm.oid;
let anchor_name_der = anchor
.subject
.to_der()
.map_err(|e| Error::OcspParseError(crate::DerError::new(e)))?;
let anchor_key_raw = anchor
.subject_public_key_info
.subject_public_key
.raw_bytes();
let expected_name_hash = hash_certid_input(hash_oid, &anchor_name_der)?;
let expected_key_hash = hash_certid_input(hash_oid, anchor_key_raw)?;
if single.cert_id.issuer_name_hash.as_bytes() != expected_name_hash.as_slice()
|| single.cert_id.issuer_key_hash.as_bytes() != expected_key_hash.as_slice()
{
return Err(Error::OcspCertIdMismatch);
}
let produced_at = basic
.tbs_response_data
.produced_at
.as_ref()
.to_unix_duration()
.as_secs();
if self.now_unix < produced_at {
return Err(Error::OcspMalformed);
}
let this_update = single.this_update.as_ref().to_unix_duration().as_secs();
if self.now_unix < this_update {
return Err(Error::OcspExpired);
}
let next_update = single.next_update.as_ref().ok_or(Error::OcspExpired)?;
if self.now_unix > next_update.as_ref().to_unix_duration().as_secs() {
return Err(Error::OcspExpired);
}
match single.cert_status {
CertStatus::Good(_) => Ok(()),
CertStatus::Revoked(ref info) => Err(Error::Revoked {
serial: cert_serial.clone(),
reason_code: info.revocation_reason,
}),
CertStatus::Unknown(_) => Err(Error::OcspStatusUnknown),
}
}
}
fn parse_basic_response(response_der: &[u8]) -> crate::Result<BasicOcspResponse> {
let resp = OcspResponse::from_der(response_der)
.map_err(|e| Error::OcspParseError(crate::DerError::new(e)))?;
if resp.response_status != OcspResponseStatus::Successful {
return Err(Error::OcspMalformed);
}
let resp_bytes = resp.response_bytes.ok_or(Error::OcspMalformed)?;
if resp_bytes.response_type != OID_PKIX_OCSP_BASIC {
return Err(Error::OcspMalformed);
}
BasicOcspResponse::from_der(resp_bytes.response.as_bytes())
.map_err(|e| Error::OcspParseError(crate::DerError::new(e)))
}
enum HashOutput {
Sha1([u8; 20]),
Sha256([u8; 32]),
Sha384([u8; 48]),
Sha512([u8; 64]),
}
impl HashOutput {
const fn as_slice(&self) -> &[u8] {
match self {
Self::Sha1(b) => b,
Self::Sha256(b) => b,
Self::Sha384(b) => b,
Self::Sha512(b) => b,
}
}
}
fn hash_certid_input(oid: &der::asn1::ObjectIdentifier, data: &[u8]) -> crate::Result<HashOutput> {
use sha1::Digest as _;
match *oid {
OID_SHA1 => Ok(HashOutput::Sha1(sha1::Sha1::digest(data).into())),
OID_SHA256 => Ok(HashOutput::Sha256(sha2::Sha256::digest(data).into())),
OID_SHA384 => Ok(HashOutput::Sha384(sha2::Sha384::digest(data).into())),
OID_SHA512 => Ok(HashOutput::Sha512(sha2::Sha512::digest(data).into())),
_ => Err(Error::OcspMalformed),
}
}
fn responder_id_matches(
id: &ResponderId,
subject: &x509_cert::name::Name,
spki_raw: &[u8],
) -> bool {
match id {
ResponderId::ByName(name) => names_match(name, subject),
ResponderId::ByKey(key_hash) => {
use sha1::Digest as _;
let expected: [u8; 20] = sha1::Sha1::digest(spki_raw).into();
key_hash.as_bytes() == expected.as_ref()
}
}
}
fn cert_has_ocsp_signing_eku(cert: &Certificate) -> crate::Result<bool> {
use x509_cert::ext::pkix::ExtendedKeyUsage;
let extns = match cert.tbs_certificate.extensions.as_deref() {
Some(e) => e,
None => return Ok(false),
};
let extn = match extns.iter().find(|e| e.extn_id == OID_EXTENDED_KEY_USAGE) {
Some(e) => e,
None => return Ok(false),
};
let eku = ExtendedKeyUsage::from_der(extn.extn_value.as_bytes())
.map_err(|_| Error::OcspResponderEkuMalformed)?;
Ok(eku.0.contains(&OID_KP_OCSP_SIGNING))
}
fn validate_delegate_responder_cert<V: SignatureVerifier>(
delegate: &Certificate,
issuer_subject: &x509_cert::name::Name,
issuer_spki: spki::SubjectPublicKeyInfoRef<'_>,
verifier: &V,
produced_at_unix: u64,
) -> crate::Result<()> {
if !names_match(&delegate.tbs_certificate.issuer, issuer_subject) {
return Err(Error::OcspResponderCertNotIssuedByCa);
}
match cert_has_ocsp_signing_eku(delegate)? {
true => {}
false => return Err(Error::OcspResponderEkuMissing),
}
let nb = delegate
.tbs_certificate
.validity
.not_before
.to_unix_duration()
.as_secs();
let na = delegate
.tbs_certificate
.validity
.not_after
.to_unix_duration()
.as_secs();
if produced_at_unix < nb || produced_at_unix > na {
return Err(Error::OcspResponderCertExpired);
}
let tbs_bytes = delegate
.tbs_certificate
.to_der()
.map_err(|e| Error::OcspParseError(crate::DerError::new(e)))?;
verifier
.verify_signature(
delegate.signature_algorithm.owned_to_ref(),
issuer_spki,
&tbs_bytes,
delegate.signature.raw_bytes(),
)
.map_err(|_| Error::OcspResponderCertSigInvalid)?;
Ok(())
}
fn resolve_signing_key_for_response<'a, V: SignatureVerifier>(
basic: &'a BasicOcspResponse,
issuer_subject: &'a x509_cert::name::Name,
issuer_spki: spki::SubjectPublicKeyInfoRef<'a>,
issuer_spki_raw: &[u8],
verifier: &V,
produced_at_unix: u64,
) -> crate::Result<spki::SubjectPublicKeyInfoRef<'a>> {
let rid = &basic.tbs_response_data.responder_id;
if responder_id_matches(rid, issuer_subject, issuer_spki_raw) {
return Ok(issuer_spki);
}
let certs: &[Certificate] = match basic.certs.as_deref() {
Some(c) => c,
None => &[],
};
for delegate in certs {
let d_subject = &delegate.tbs_certificate.subject;
let d_spki_raw = delegate
.tbs_certificate
.subject_public_key_info
.subject_public_key
.raw_bytes();
if !responder_id_matches(rid, d_subject, d_spki_raw) {
continue;
}
validate_delegate_responder_cert(
delegate,
issuer_subject,
issuer_spki,
verifier,
produced_at_unix,
)?;
return Ok(delegate
.tbs_certificate
.subject_public_key_info
.owned_to_ref());
}
Err(Error::OcspResponderIdMismatch)
}
fn produced_at_unix_secs(basic: &BasicOcspResponse) -> u64 {
basic
.tbs_response_data
.produced_at
.as_ref()
.to_unix_duration()
.as_secs()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_certid_sha384() {
let expected: &[u8] = &[
0x76, 0x84, 0x12, 0x32, 0x0f, 0x7b, 0x0a, 0xa5, 0x81, 0x2f, 0xce, 0x42, 0x8d, 0xc4,
0x70, 0x6b, 0x3c, 0xae, 0x50, 0xe0, 0x2a, 0x64, 0xca, 0xa1, 0x6a, 0x78, 0x22, 0x49,
0xbf, 0xe8, 0xef, 0xc4, 0xb7, 0xef, 0x1c, 0xcb, 0x12, 0x62, 0x55, 0xd1, 0x96, 0x04,
0x7d, 0xfe, 0xdf, 0x17, 0xa0, 0xa9,
];
let result = hash_certid_input(&OID_SHA384, b"test").expect("SHA-384 must succeed");
assert_eq!(
result.as_slice(),
expected,
"SHA-384(\"test\") must match Python oracle"
);
}
#[test]
fn hash_certid_sha512() {
let expected: &[u8] = &[
0xee, 0x26, 0xb0, 0xdd, 0x4a, 0xf7, 0xe7, 0x49, 0xaa, 0x1a, 0x8e, 0xe3, 0xc1, 0x0a,
0xe9, 0x92, 0x3f, 0x61, 0x89, 0x80, 0x77, 0x2e, 0x47, 0x3f, 0x88, 0x19, 0xa5, 0xd4,
0x94, 0x0e, 0x0d, 0xb2, 0x7a, 0xc1, 0x85, 0xf8, 0xa0, 0xe1, 0xd5, 0xf8, 0x4f, 0x88,
0xbc, 0x88, 0x7f, 0xd6, 0x7b, 0x14, 0x37, 0x32, 0xc3, 0x04, 0xcc, 0x5f, 0xa9, 0xad,
0x8e, 0x6f, 0x57, 0xf5, 0x00, 0x28, 0xa8, 0xff,
];
let result = hash_certid_input(&OID_SHA512, b"test").expect("SHA-512 must succeed");
assert_eq!(
result.as_slice(),
expected,
"SHA-512(\"test\") must match Python oracle"
);
}
#[test]
fn hash_certid_unknown_oid_returns_malformed() {
let unknown = der::asn1::ObjectIdentifier::new_unwrap("1.2.3.4.5");
let result = hash_certid_input(&unknown, b"test");
assert!(
matches!(result, Err(Error::OcspMalformed)),
"unknown hash OID must return OcspMalformed"
);
}
}