use crate::error::{Located, Result};
use crate::extension::{Extension, Recognizer};
use crate::path::{
self, Anchor, BuildResult, EffectiveAnchor, Validation, ValidationContext, Verify, Watch,
WorkingKey,
};
use crate::{Certificate, oid};
use structured_asn1::memory::Work;
use structured_asn1::{Tag, universal};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResponderRevocation {
NoCheck,
Unresolved,
}
pub(crate) fn read_no_check<W: Work>(extension: &Extension<'_>, execution: &W) -> Result<()> {
const FIELD: &str = "extensions.ocspNoCheck";
let mut reader = extension.reader_scoped(execution).field(FIELD)?;
let element = reader
.expect(Tag::primitive(universal::NULL))
.field(FIELD)?;
reader.null(&element).field(FIELD)?;
super::end(&reader, FIELD)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DelegationFailure {
IssuerKeyBinding,
SigningPurpose,
KeyUsage,
}
#[derive(Debug, Clone, Copy)]
pub struct DelegateFacts<'a> {
responder: &'a Certificate<'a>,
issuer: &'a Certificate<'a>,
issuing_key: WorkingKey<'a>,
key: WorkingKey<'a>,
revocation: ResponderRevocation,
}
impl<'a> DelegateFacts<'a> {
#[must_use]
pub fn responder(self) -> &'a Certificate<'a> {
self.responder
}
#[must_use]
pub fn issuer(self) -> &'a Certificate<'a> {
self.issuer
}
#[must_use]
pub fn issuing_key(self) -> WorkingKey<'a> {
self.issuing_key
}
#[must_use]
pub fn key(self) -> WorkingKey<'a> {
self.key
}
#[must_use]
pub fn revocation(self) -> ResponderRevocation {
self.revocation
}
}
#[derive(Debug)]
#[expect(
clippy::large_enum_variant,
reason = "fixed-size local facts retain both original issuer and responder parameter sources; avoid boxing solely for enum layout"
)]
pub enum Delegation<'a> {
Accepted(DelegateFacts<'a>),
Rejected(DelegationFailure),
PathRejected(path::Rejection<'a>),
Incomplete(path::Truncation),
}
pub fn check_delegate<'a, V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work>(
responder: &'a Certificate<'a>,
issuer: &'a Certificate<'a>,
issuing_key: WorkingKey<'a>,
context: &ValidationContext<'a, '_, V, W, R, E>,
) -> BuildResult<Delegation<'a>, V::Error, R::Error> {
let execution = context.execution;
let Some(anchor) =
EffectiveAnchor::bind(Anchor::from_certificate(issuer), issuing_key, execution)?
else {
return Ok(Delegation::Rejected(DelegationFailure::IssuerKeyBinding));
};
let extensions = responder.tbs().extensions();
let Some(purposes) = extensions.extended_key_usage(execution)? else {
return Ok(Delegation::Rejected(DelegationFailure::SigningPurpose));
};
if !purposes.has(oid::OCSP_SIGNING, execution)? {
return Ok(Delegation::Rejected(DelegationFailure::SigningPurpose));
}
if !super::authority::signing_usage(responder, execution)? {
return Ok(Delegation::Rejected(DelegationFailure::KeyUsage));
}
let revocation = match extensions.get(oid::OCSP_NO_CHECK, execution)? {
Some(extension) => {
read_no_check(extension, execution)?;
ResponderRevocation::NoCheck
}
None => ResponderRevocation::Unresolved,
};
Ok(
match path::validate_effective(&[responder], anchor, context)? {
Validation::Accepted(facts) => Delegation::Accepted(DelegateFacts {
responder,
issuer,
issuing_key,
key: facts.key,
revocation,
}),
Validation::Rejected(reason) => Delegation::PathRejected(reason),
Validation::Incomplete(reason) => Delegation::Incomplete(reason),
},
)
}
#[cfg(test)]
mod tests;