use super::{AttributeCertificate, DigestedObject, IssuerForm, proxy, target};
use crate::{Error, Result};
use structured_asn1::memory::Work;
use structured_asn1::{BitString, Span};
use structured_pkix::general_name::{self, GeneralName};
use structured_pkix::revocation::RevocationTarget;
use structured_pkix::{Certificate, Name};
pub mod aa_path;
pub mod authentication;
pub mod digest;
mod profile;
mod revocation;
pub mod usage;
pub use profile::{AUDIT_IDENTITY, NO_REV_AVAILABLE, Profile, Revocation};
const FIELD: &str = "attributeCertificate.validation";
#[derive(Debug)]
pub enum Check<T> {
Accepted(T),
Rejected(Rejection),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rejection {
pub reason: Reason,
pub source: Option<Span>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Reason {
IssuerForm,
Serial,
Validity,
HolderIssuer,
NameForm,
ObjectDigest,
Criticality,
UnknownCritical,
AuditIdentity,
TargetCertificate,
AuthorityKeyIdentifier,
OcspLocation,
DistributionPoint,
RevocationConflict,
HolderAbsent,
HolderCertificate,
HolderName,
HolderUniqueId,
AuthorityName,
AuthorityUniqueId,
AuthorityIsCa,
AuthorityUsage,
EmptyPath,
MissingControls,
ControlDistance,
}
fn refused<T>(reason: Reason, source: Span) -> Check<T> {
Check::Rejected(Rejection {
reason,
source: Some(source),
})
}
pub(super) fn charge(work: &impl Work, amount: u64, at: u64) -> Result<()> {
work.charge_work(amount)
.map_err(|e| Error::resource(e, FIELD, at))
}
fn same_bits(
left: BitString<'_>,
right: BitString<'_>,
left_work: &impl Work,
right_work: &impl Work,
at: u64,
) -> Result<bool> {
charge(left_work, 1, at)?;
charge(right_work, 1, at)?;
if left.len() != right.len() {
return Ok(false);
}
charge(left_work, left.as_bytes().len() as u64, at)?;
charge(right_work, right.as_bytes().len() as u64, at)?;
Ok(left.as_bytes() == right.as_bytes())
}
fn directory<'n, 'a>(names: &'n [GeneralName<'a>]) -> Option<&'n Name<'a>> {
match names {
[GeneralName::Directory(name)] if !name.is_empty() => Some(name),
_ => None,
}
}
fn check_name(name: &GeneralName<'_>, work: &impl Work, source: Span) -> Result<Check<()>> {
charge(work, 1, source.start)?;
if matches!(
name,
GeneralName::X400(_) | GeneralName::EdiParty(_) | GeneralName::RegisteredId(_)
) {
return Ok(refused(Reason::NameForm, source));
}
Ok(Check::Accepted(()))
}
fn check_names(names: &[GeneralName<'_>], work: &impl Work, source: Span) -> Result<Check<()>> {
charge(work, names.len() as u64, source.start)?;
for name in names {
if let rejected @ Check::Rejected(_) = check_name(name, work, source)? {
return Ok(rejected);
}
}
Ok(Check::Accepted(()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HolderMethod {
BaseCertificate,
EntityName,
}
impl<'a> AttributeCertificate<'a> {
pub fn profile<W: Work>(&self, work: &W) -> Result<Check<Profile<'_, 'a>>> {
profile::check(self, work)
}
pub fn bind_holder<W: Work>(
&self,
certificate: &Certificate<'_>,
method: HolderMethod,
work: &W,
) -> Result<Check<()>> {
let execution = work.execution_for(self.parser);
let at = self.signed.span();
charge(&execution, 1, at.start)?;
let certificate_execution = certificate.execution_in(&execution);
charge(&certificate_execution, 1, certificate.span().start)?;
match method {
HolderMethod::BaseCertificate => {
let Some(base) = &self.information.holder.base_certificate else {
return Ok(refused(Reason::HolderAbsent, at));
};
let Some(name) = directory(&base.issuer) else {
return Ok(refused(Reason::HolderIssuer, at));
};
let left = base.serial.as_bytes();
let right = certificate.tbs().serial().as_bytes();
charge(&execution, left.len() as u64 + 1, at.start)?;
charge(
&certificate_execution,
right.len() as u64 + 1,
certificate.span().start,
)?;
if left != right
|| !name
.matches_between(
certificate.tbs().issuer(),
(&execution, self.parser),
(&certificate_execution, certificate.source_parser()),
)
.map_err(Error::pkix)?
{
return Ok(refused(Reason::HolderCertificate, at));
}
if let Some(uid) = base.issuer_uid {
let Some(other) = certificate.tbs().issuer_unique_id() else {
return Ok(refused(Reason::HolderUniqueId, at));
};
if !same_bits(uid, other, &execution, &certificate_execution, at.start)? {
return Ok(refused(Reason::HolderUniqueId, at));
}
}
}
HolderMethod::EntityName => {
let Some(names) = &self.information.holder.entity_names else {
return Ok(refused(Reason::HolderAbsent, at));
};
if let rejected @ Check::Rejected(_) = check_names(names, &execution, at)? {
return Ok(rejected);
}
for name in names {
charge(&execution, 1, at.start)?;
if let GeneralName::Directory(name) = name
&& !name.is_empty()
&& name
.matches_between(
certificate.tbs().subject(),
(&execution, self.parser),
(&certificate_execution, certificate.source_parser()),
)
.map_err(Error::pkix)?
{
return Ok(Check::Accepted(()));
}
}
let Some(sans) = certificate
.tbs()
.extensions()
.subject_alt_name(&certificate_execution)
.map_err(Error::pkix)?
else {
return Ok(refused(Reason::HolderName, at));
};
for name in names {
charge(&execution, 1, at.start)?;
for alternative in &sans {
charge(&certificate_execution, 1, at.start)?;
if general_name::matches_between(
name,
alternative,
(&execution, self.parser),
(&certificate_execution, certificate.source_parser()),
at.start,
FIELD,
)
.map_err(Error::pkix)?
{
return Ok(Check::Accepted(()));
}
}
}
return Ok(refused(Reason::HolderName, at));
}
}
Ok(Check::Accepted(()))
}
pub fn bind_authority<W: Work>(
&self,
authority: &Certificate<'_>,
work: &W,
) -> Result<Check<()>> {
let execution = work.execution_for(self.parser);
let at = self.signed.span();
charge(&execution, 1, at.start)?;
let authority_execution = authority.execution_in(&execution);
charge(&authority_execution, 1, authority.span().start)?;
let issuer = &self.information.issuer;
if issuer.form != IssuerForm::V2
|| issuer.base_certificate.is_some()
|| issuer.object_digest.is_some()
{
return Ok(refused(Reason::IssuerForm, at));
}
let Some(name) = issuer.names.as_deref().and_then(directory) else {
return Ok(refused(Reason::IssuerForm, at));
};
if !name
.matches_between(
authority.tbs().subject(),
(&execution, self.parser),
(&authority_execution, authority.source_parser()),
)
.map_err(Error::pkix)?
{
return Ok(refused(Reason::AuthorityName, at));
}
match (
self.information.issuer_uid,
authority.tbs().subject_unique_id(),
) {
(None, None) => {}
(Some(left), Some(right)) => {
if !same_bits(left, right, &execution, &authority_execution, at.start)? {
return Ok(refused(Reason::AuthorityUniqueId, at));
}
}
_ => return Ok(refused(Reason::AuthorityUniqueId, at)),
}
let extensions = authority.tbs().extensions();
if extensions
.basic_constraints(&authority_execution)
.map_err(Error::pkix)?
.is_some_and(structured_pkix::constraints::BasicConstraints::is_authority)
{
return Ok(refused(Reason::AuthorityIsCa, authority.span()));
}
if extensions
.key_usage(&authority_execution)
.map_err(Error::pkix)?
.is_some_and(|value| !value.digital_signature() && !value.content_commitment())
{
return Ok(refused(Reason::AuthorityUsage, authority.span()));
}
Ok(Check::Accepted(()))
}
}