use super::{Index, Placement, Pool};
use crate::{
Cades, Error,
prepare::Preparation,
source::{DocumentId, Node},
};
use structured_asn1::memory::{OwnedVec, Work};
use structured_ltv::{
baseline::X509Material,
material::{self, ArtifactRef, Incorporation, IncorporationError, IndexError},
pki::{self, ParticipantClosure, ParticipantRequirements, UsesError},
source::Query,
timestamp::TimestampScope,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Finding {
Missing {
artifact: usize,
},
Placement {
artifact: usize,
},
Malformed {
node: Node,
},
}
pub struct Checks<'closure, 'requirements, 'view, 'certificate, 'document, 'model> {
inputs: &'closure ParticipantClosure<
'requirements,
'view,
'certificate,
'document,
Node,
DocumentId<'model, 'document>,
(),
Cades,
>,
presence:
Incorporation<'certificate, 'document, Node, DocumentId<'model, 'document>, (), Placement>,
findings: OwnedVec<Finding>,
}
impl<'d, 'm> Checks<'_, '_, '_, '_, 'd, 'm> {
#[must_use]
pub fn presence(&self) -> &Incorporation<'_, 'd, Node, DocumentId<'m, 'd>, (), Placement> {
&self.presence
}
#[must_use]
pub fn findings(&self) -> &[Finding] {
&self.findings
}
#[must_use]
pub fn all_x509_requirements_met(&self) -> bool {
self.findings.is_empty() && self.presence.all_present()
}
}
impl<'v, 'd, 'm> X509Material<'v, 'd> for Checks<'_, '_, 'v, '_, 'd, 'm> {
type Id = Node;
type Document = DocumentId<'m, 'd>;
type Generation = ();
type Issuer = Cades;
fn requirements(
&self,
) -> &ParticipantRequirements<'v, 'd, Node, DocumentId<'m, 'd>, (), Cades> {
self.inputs.requirements()
}
fn all_x509_requirements_met(&self) -> bool {
Self::all_x509_requirements_met(self)
}
}
#[derive(Debug)]
pub enum CheckError {
Processing(Error),
Uses(UsesError<Node>),
Presence(IncorporationError<Node>),
Index(IndexError<Node>),
}
pub fn check_participants<'cl, 'r, 'v, 'c, 'd, 'm, Q, W>(
prepared: &Preparation<'m, 'd>,
pool: &Pool<'_, 'm, 'd>,
inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, DocumentId<'m, 'd>, (), Cades>,
index: &Index<'_, 'm, 'd, W>,
query: &Q,
) -> Result<Checks<'cl, 'r, 'v, 'c, 'd, 'm>, CheckError>
where
Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
W: Work,
{
check_in(prepared, pool, inputs, index, None, query)
}
pub fn check_participants_preserved<'cl, 'r, 'v, 'c, 'd, 'm, Q, W>(
prepared: &Preparation<'m, 'd>,
pool: &Pool<'_, 'm, 'd>,
inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, DocumentId<'m, 'd>, (), Cades>,
index: &Index<'_, 'm, 'd, W>,
preservation: &crate::evidence::Closure<'_, '_, 'm, 'd, '_>,
query: &Q,
) -> Result<Checks<'cl, 'r, 'v, 'c, 'd, 'm>, CheckError>
where
Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
W: Work,
{
check_in(prepared, pool, inputs, index, Some(preservation), query)
}
fn check_in<'cl, 'r, 'v, 'c, 'd, 'm, Q, W>(
prepared: &Preparation<'m, 'd>,
pool: &Pool<'_, 'm, 'd>,
inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, DocumentId<'m, 'd>, (), Cades>,
index: &Index<'_, 'm, 'd, W>,
preservation: Option<&crate::evidence::Closure<'_, '_, 'm, 'd, '_>>,
query: &Q,
) -> Result<Checks<'cl, 'r, 'v, 'c, 'd, 'm>, CheckError>
where
Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
W: Work,
{
if let Some(proofs) = preservation
&& !core::ptr::eq(proofs.prepared(), prepared)
{
return Err(CheckError::Processing(Error::Foreign {
at: prepared.signer().span().start,
}));
}
let selected = prepared.document().locate(prepared.signer().span());
query
.admit(selected.node, 1)
.map_err(|cause| CheckError::Index(IndexError::Scope(cause)))?;
if !core::ptr::eq(pool.prepared(), prepared)
|| inputs.requirements().signature() != selected.node
|| query.snapshot() != selected.snapshot
|| !matches!(inputs.requirements().scope(), TimestampScope::All(_))
{
return Err(CheckError::Processing(Error::Foreign {
at: selected.node.span().start,
}));
}
index
.require_signature(selected.node, query)
.map_err(CheckError::Index)?;
let uses = pki::authority_uses(inputs, query).map_err(CheckError::Uses)?;
let artifacts = material::artifact_uses(&uses, query).map_err(CheckError::Uses)?;
let presence =
material::incorporation(&artifacts, index, query).map_err(CheckError::Presence)?;
let mut findings = query.execution().vector();
let archive_augmentation = prepared.requested() == crate::Level::Archival;
let resource = |cause| {
CheckError::Processing(Error::resource(
cause,
"cades.material",
selected.node.span().start,
))
};
for (ordinal, entry) in artifacts.entries().iter().enumerate() {
query.execution().charge_work(1).map_err(resource)?;
let artifact = entry.artifact();
let mut copies = index.artifact(artifact, query).map_err(CheckError::Index)?;
let mut present = false;
let mut allowed = false;
while let Some(copy) = copies.next().transpose().map_err(CheckError::Index)? {
present = true;
let poe = match copy.placement() {
Placement::PoeCertificate(location) | Placement::PoeRevocation(location)
if archive_augmentation =>
{
preserved_extra(prepared, preservation, location, query)?
}
_ => false,
};
allowed |= match artifact {
ArtifactRef::Certificate(_) => {
matches!(copy.placement(), Placement::RootCertificate(_))
|| poe
|| (archive_augmentation
&& (matches!(copy.placement(), Placement::TokenCertificate { .. })
|| matches!(copy.placement(), Placement::CertificateValues(location)
if archived_values(prepared, location))
|| (copy.embedded_certificate().is_some()
&& embedded_placement(prepared, copy.placement()))))
}
ArtifactRef::Crl(_) | ArtifactRef::Ocsp(_) => {
matches!(
copy.placement(),
Placement::RootRevocation(_) | Placement::TokenRevocation { .. }
) || poe
|| (archive_augmentation
&& matches!(copy.placement(), Placement::CrlValues(location)
| Placement::OcspValues(location) if archived_values(prepared, location)))
}
};
}
if !present {
findings
.try_push(Finding::Missing { artifact: ordinal })
.map_err(resource)?;
} else if !allowed {
findings
.try_push(Finding::Placement { artifact: ordinal })
.map_err(resource)?;
}
}
retain_malformed(pool, query, selected.node, &mut findings)?;
Ok(Checks {
inputs,
presence,
findings: findings.into_owned(),
})
}
fn retain_malformed<'m, 'd, Q, W: Work>(
pool: &Pool<'_, 'm, 'd>,
query: &Q,
selected: Node,
findings: &mut OwnedVec<Finding, W>,
) -> Result<(), CheckError>
where
Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
{
let resource = |cause| {
CheckError::Processing(Error::resource(
cause,
"cades.material",
selected.span().start,
))
};
for entry in pool.open() {
query.execution().charge_work(1).map_err(resource)?;
if entry.cause.is_some() {
findings
.try_push(Finding::Malformed { node: entry.node })
.map_err(resource)?;
}
}
super::legacy::malformed(
pool,
|node| {
query
.admit(node, 1)
.map_err(|cause| CheckError::Index(IndexError::Scope(cause)))
},
|node, fault| {
query.execution().charge_work(24).map_err(resource)?;
if fault.attribute == crate::properties::legacy::LEGACY[2]
|| fault.attribute == crate::properties::legacy::LEGACY[3]
{
findings
.try_push(Finding::Malformed { node })
.map_err(resource)?;
}
Ok(())
},
)?;
Ok(())
}
fn preserved_extra<'m, 'd, Q>(
prepared: &Preparation<'m, 'd>,
preservation: Option<&crate::evidence::Closure<'_, '_, 'm, 'd, '_>>,
location: super::PoeLocation,
query: &Q,
) -> Result<bool, CheckError>
where
Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
{
let resource = |cause| {
CheckError::Processing(Error::resource(
cause,
"cades.material",
location.property.span().start,
))
};
query.execution().charge_work(1).map_err(resource)?;
let Some(proofs) = preservation else {
return Ok(false);
};
let entry = prepared
.parsed
.proofs
.entries
.get(location.proof)
.ok_or_else(|| {
CheckError::Processing(Error::Foreign {
at: location.property.span().start,
})
})?;
if entry.required.signature != location.signature
|| entry.required.property != location.property
{
return Err(CheckError::Processing(Error::Foreign {
at: location.property.span().start,
}));
}
proofs
.contains(&entry.required, query.execution())
.map_err(resource)
}
fn archived_values(prepared: &Preparation<'_, '_>, location: super::LegacyLocation) -> bool {
if !location.unsigned {
return false;
}
let Some(token) = location.token else {
return false;
};
prepared
.timestamps()
.get(token)
.is_some_and(|entry| entry.purpose() == structured_ltv::timestamp::Purpose::Archive)
}
fn embedded_placement(prepared: &Preparation<'_, '_>, placement: Placement) -> bool {
match placement {
Placement::CertificateValues(location)
| Placement::CrlValues(location)
| Placement::OcspValues(location) => archived_values(prepared, location),
_ => true,
}
}