use super::{ReferenceSearch, ref_catalogue::Memo};
use crate::{
Xades,
binary::{self, BinaryXml, Location},
certificate::{Agreement, DigestProvider},
digest,
material::{
ExclusiveFindingKind,
exclusivity::{self, CaError, CaFact, ReferenceCa},
},
prepare::{Preparation, Prepared},
unsigned,
xml::{Access, Xml},
};
use core::ops::Range;
use structured_asn1::digest::Digest;
use structured_asn1::memory::{MemoryError, OwnedVec, Work};
use structured_ltv::material::ArtifactUses;
use structured_ltv::timestamp::TimestampScope;
use structured_pkix::Certificate;
pub struct CertificateRoleRow<N> {
property: usize,
entry: usize,
node: N,
search: ReferenceSearch,
range: Range<usize>,
}
impl<N: Copy> CertificateRoleRow<N> {
#[must_use]
pub fn property(&self) -> usize {
self.property
}
#[must_use]
pub fn entry(&self) -> usize {
self.entry
}
#[must_use]
pub fn node(&self) -> N {
self.node
}
#[must_use]
pub fn search(&self) -> ReferenceSearch {
self.search
}
}
pub struct CertificateRoleComparison<'certificate, 'document> {
certificate: &'certificate Certificate<'document>,
kind: ExclusiveFindingKind,
agreement: Agreement,
}
impl<'certificate, 'document> CertificateRoleComparison<'certificate, 'document> {
#[must_use]
pub fn certificate(&self) -> &'certificate Certificate<'document> {
self.certificate
}
#[must_use]
pub fn kind(&self) -> ExclusiveFindingKind {
self.kind
}
#[must_use]
pub fn agreement(&self) -> Agreement {
self.agreement
}
}
pub struct CertificateRoles<'prepared, 'certificate, 'document, N, D, G> {
prepared: &'prepared Preparation<N, D, G>,
rows: OwnedVec<CertificateRoleRow<N>>,
comparisons: OwnedVec<CertificateRoleComparison<'certificate, 'document>>,
}
impl<'p, 'c, 'd, N: Copy, D, G> CertificateRoles<'p, 'c, 'd, N, D, G> {
#[must_use]
pub fn prepared(&self) -> &'p Preparation<N, D, G> {
self.prepared
}
#[must_use]
pub fn rows(&self) -> &[CertificateRoleRow<N>] {
&self.rows
}
#[must_use]
pub fn comparisons(&self, row: usize) -> Option<&[CertificateRoleComparison<'c, 'd>]> {
self.comparisons.get(self.rows.get(row)?.range.clone())
}
}
pub enum CertificateRoleError<'certificate, 'document, PE> {
Scope,
Resource(MemoryError),
Certificate {
certificate: &'certificate Certificate<'document>,
cause: structured_pkix::Error,
},
Digest(digest::Error<structured_cms::stream::RangesError, PE>),
}
impl<PE: core::fmt::Debug> core::fmt::Debug for CertificateRoleError<'_, '_, PE> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Scope => f.write_str("Scope"),
Self::Resource(cause) => f.debug_tuple("Resource").field(cause).finish(),
Self::Certificate { cause, .. } => f.debug_tuple("Certificate").field(cause).finish(),
Self::Digest(cause) => f.debug_tuple("Digest").field(cause).finish(),
}
}
}
pub type CertificateRoleResult<'p, 'c, 'd, X, PE> = binary::BinaryResult<
CertificateRoles<'p, 'c, 'd, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
X,
CertificateRoleError<'c, 'd, PE>,
>;
fn compare_role_candidates<'c, 'd, 'q, X: Xml, P: Digest<'q>, W: Work>(
memo: &mut Memo<'q, 'd, P, W>,
candidates: &mut Option<OwnedVec<ReferenceCa<'c, 'd>, W>>,
artifacts: &ArtifactUses<'_, '_, '_, 'c, 'd, X::Id, X::Document, X::Generation, Xades>,
comparisons: &mut OwnedVec<CertificateRoleComparison<'c, 'd>, W>,
identity: crate::certificate::FormatDigest<P::Identity>,
expected: &[u8],
work: &W,
) -> Result<(), digest::Error<CertificateRoleError<'c, 'd, P::Error>, P::Error>> {
let method = memo
.method(identity, work)
.map_err(|cause| digest::Error::Source(CertificateRoleError::Digest(cause)))?;
if candidates.is_none() {
*candidates = Some(
exclusivity::reference_certificate_candidates(artifacts, work).map_err(|cause| {
digest::Error::Source(match cause {
CaError::Resource(cause) => CertificateRoleError::Resource(cause),
CaError::Certificate(certificate, cause) => {
CertificateRoleError::Certificate { certificate, cause }
}
})
})?,
);
}
for (ordinal, candidate) in candidates
.as_ref()
.expect("initialized candidates")
.iter()
.enumerate()
{
work.charge_work(1).map_err(digest::Error::Resource)?;
let agreement = memo
.compare(
ordinal,
candidate.certificate.as_bytes(),
method,
expected,
work,
)
.map_err(|cause| digest::Error::Source(CertificateRoleError::Digest(cause)))?;
let kind = match candidate.fact {
CaFact::Asserted => ExclusiveFindingKind::ForbiddenCa,
CaFact::Unstated => ExclusiveFindingKind::UnstatedCa,
CaFact::NotAsserted => unreachable!("non-CA is not an exclusion candidate"),
};
comparisons
.try_push(CertificateRoleComparison {
certificate: candidate.certificate,
kind,
agreement,
})
.map_err(digest::Error::Resource)?;
}
Ok(())
}
pub(super) fn role_error<'c, 'd, PE>(
cause: digest::Error<CertificateRoleError<'c, 'd, PE>, PE>,
) -> CertificateRoleError<'c, 'd, PE> {
match cause {
digest::Error::Source(cause) => cause,
digest::Error::Provider(cause) => {
CertificateRoleError::Digest(digest::Error::Provider(cause))
}
digest::Error::Resource(cause) => CertificateRoleError::Resource(cause),
digest::Error::Stream(cause) => CertificateRoleError::Digest(digest::Error::Stream(cause)),
digest::Error::OutputWidth { expected, actual } => {
CertificateRoleError::Digest(digest::Error::OutputWidth { expected, actual })
}
}
}
pub fn check_certificate_reference_roles<
'p,
'c,
'd,
'q,
X: BinaryXml,
P: DigestProvider<'q, X>,
W: Work,
>(
xml: &X,
prepared: &'p Prepared<X>,
artifacts: &ArtifactUses<'_, '_, '_, 'c, 'd, X::Id, X::Document, X::Generation, Xades>,
provider: &'q P,
work: &W,
) -> CertificateRoleResult<'p, 'c, 'd, X, P::Error> {
let source = prepared.bound().containers();
let location = Location {
snapshot: source.snapshot(),
node: source.signature(),
};
let access = Access::new(xml, location.snapshot, work);
access
.query(location.node, |_, _, _| Ok(()))
.map_err(|cause| binary::Error::Access { location, cause })?;
let failed = |cause| binary::Error::Callback { location, cause };
let required = artifacts.inputs().requirements();
if required.scope().location().snapshot != location.snapshot
|| !matches!(required.scope(), TimestampScope::All(_))
|| !access
.equal_id(required.signature(), location.node)
.map_err(|cause| failed(CertificateRoleError::Resource(cause)))?
{
return Err(failed(CertificateRoleError::Scope));
}
let mut candidates: Option<OwnedVec<ReferenceCa<'c, 'd>, W>> = None;
let mut memo = Memo::<P, W>::new(provider, work);
let mut rows = work.vector();
let mut comparisons = work.vector();
for (property, refs) in prepared.validation_references().iter().enumerate() {
work.charge_work(1)
.map_err(|cause| failed(CertificateRoleError::Resource(cause)))?;
if refs.role() != unsigned::Role::CertificateReferences {
continue;
}
for (entry, reference) in refs.entries().iter().enumerate() {
work.charge_work(1)
.map_err(|cause| failed(CertificateRoleError::Resource(cause)))?;
let (method, value) = reference.digest().expect("mandatory CertDigest");
let start = comparisons.len();
let checked = digest::check_with(
xml,
digest::Reference {
snapshot: refs.snapshot(),
method,
value,
},
provider,
work,
|identity, expected, work| {
compare_role_candidates::<X, P, W>(
&mut memo,
&mut candidates,
artifacts,
&mut comparisons,
identity,
expected,
work,
)
},
)
.map_err(|cause| cause.map_callback(role_error))?;
let search = match checked {
digest::Checked::Value(()) => ReferenceSearch::Compared,
digest::Checked::Answer(answer) => ReferenceSearch::Unavailable(answer),
};
rows.try_push(CertificateRoleRow {
property,
entry,
node: reference.node(),
search,
range: start..comparisons.len(),
})
.map_err(|cause| failed(CertificateRoleError::Resource(cause)))?;
}
}
access
.query(location.node, |_, _, _| Ok(()))
.map_err(|cause| binary::Error::Access { location, cause })?;
Ok(CertificateRoles {
prepared,
rows: rows.into_owned(),
comparisons: comparisons.into_owned(),
})
}