use super::{ArchiveError, Archived, ArtifactRef, Coverage, Index, IndexError, PathCertificates};
use crate::pki::{AuthorityClosure, CrlReading, MemberStatus, PathStatuses};
use crate::source::{Location, Query, ScopeError};
use crate::timestamp::Verified;
use structured_asn1::memory::{HashWork, MemoryError, OwnedVec, Work};
use structured_pkix::{crl, ocsp};
pub struct AuthorityMaterial<'closure, 'view, 'certificate, 'document, N, D, G, P> {
inputs: &'closure AuthorityClosure<'view, 'certificate, 'document>,
paths: OwnedVec<PathMaterial<'view, 'certificate, 'document, N, D, G, P>>,
all_covered: bool,
}
impl<'closure, 'view, 'certificate, 'document, N, D, G, P>
AuthorityMaterial<'closure, 'view, 'certificate, 'document, N, D, G, P>
{
#[must_use]
pub fn inputs(&self) -> &'closure AuthorityClosure<'view, 'certificate, 'document> {
self.inputs
}
#[must_use]
pub fn paths(&self) -> &[PathMaterial<'view, 'certificate, 'document, N, D, G, P>] {
&self.paths
}
#[must_use]
pub fn all_artifacts_covered(&self) -> bool {
self.all_covered
}
}
pub fn archived_authorities<'closure, 'view, 'certificate, 'document, N, D, G, P, O, W, Q, C, I>(
inputs: &'closure AuthorityClosure<'view, 'certificate, 'document>,
index: &Index<'_, N, D, G, P, O, W, I>,
coverage: &Coverage<N, D, G, C, I>,
evidence: &Verified<N, D, G, I>,
query: &Q,
) -> Result<AuthorityMaterial<'closure, 'view, 'certificate, 'document, N, D, G, P>, ArchiveError<N>>
where
N: Copy + Eq + HashWork,
D: Copy + Eq,
G: Copy + Eq,
P: Copy,
W: Work,
C: Work,
Q: Query<Id = N, Document = D, Generation = G>,
{
let signature = evidence.signature();
let resource = |cause| ArchiveError::Index(IndexError::Resource { signature, cause });
let scope = |cause| ArchiveError::Index(IndexError::Scope(cause));
query.admit(signature, 1).map_err(scope)?;
let mut paths = query.execution().vector();
paths
.try_reserve_exact(inputs.nodes().len())
.map_err(resource)?;
let mut all_covered = true;
for node in inputs.nodes() {
query.admit(signature, 2).map_err(scope)?;
let material = archived_path(node.statuses, index, coverage, evidence, query)?;
if !material.all_artifacts_covered() {
all_covered = false;
}
paths.try_push(material).map_err(resource)?;
}
Ok(AuthorityMaterial {
inputs,
paths: paths.into_owned(),
all_covered,
})
}
pub struct PathMaterial<'view, 'certificate, 'document, N, D, G, P> {
inputs: &'view PathStatuses<'view, 'certificate, 'document>,
certificates: PathCertificates<'view, 'document, N, D, G, P>,
statuses: OwnedVec<StatusMaterial<'view, 'certificate, 'document, N, D, G, P>>,
all_covered: bool,
}
impl<'view, 'certificate, 'document, N, D, G, P>
PathMaterial<'view, 'certificate, 'document, N, D, G, P>
{
#[must_use]
pub fn inputs(&self) -> &'view PathStatuses<'view, 'certificate, 'document> {
self.inputs
}
#[must_use]
pub fn certificates(&self) -> &PathCertificates<'view, 'document, N, D, G, P> {
&self.certificates
}
#[must_use]
pub fn statuses(&self) -> &[StatusMaterial<'view, 'certificate, 'document, N, D, G, P>] {
&self.statuses
}
#[must_use]
pub fn all_artifacts_covered(&self) -> bool {
self.all_covered
}
}
pub fn archived_path<'view, 'certificate, 'document, N, D, G, P, O, W, Q, C, I>(
inputs: &'view PathStatuses<'view, 'certificate, 'document>,
index: &Index<'_, N, D, G, P, O, W, I>,
coverage: &Coverage<N, D, G, C, I>,
evidence: &Verified<N, D, G, I>,
query: &Q,
) -> Result<PathMaterial<'view, 'certificate, 'document, N, D, G, P>, ArchiveError<N>>
where
N: Copy + Eq + HashWork,
D: Copy + Eq,
G: Copy + Eq,
P: Copy,
W: Work,
C: Work,
Q: Query<Id = N, Document = D, Generation = G>,
{
let certificates = index.archived_path(inputs.path(), coverage, evidence, query)?;
let mut all_covered = certificates.all_certificates_covered();
let signature = evidence.signature();
let resource = |cause| ArchiveError::Index(IndexError::Resource { signature, cause });
let scope = |cause| ArchiveError::Index(IndexError::Scope(cause));
query.admit(signature, 1).map_err(scope)?;
let mut statuses = query.execution().vector();
let mut count = 0;
for input in inputs.entries() {
query.admit(signature, 2).map_err(scope)?;
if !matches!(input, MemberStatus::ResponderNoCheck(_)) {
count += 1;
}
}
statuses.try_reserve_exact(count).map_err(resource)?;
for input in inputs.entries() {
query.admit(signature, 2).map_err(scope)?;
let Some(source) = member_source(input) else {
continue;
};
let status = archived(source, index, coverage, evidence, query)?;
if !status.all_artifacts_covered() {
all_covered = false;
}
statuses.try_push(status).map_err(resource)?;
}
Ok(PathMaterial {
inputs,
certificates,
statuses: statuses.into_owned(),
all_covered,
})
}
#[derive(Clone, Copy)]
pub enum StatusSource<'view, 'certificate, 'document> {
Crl(&'view crl::Coverage<'certificate, 'document>),
CrlReading(CrlReading<'view, 'certificate, 'document>),
Ocsp(&'view ocsp::BoundEvidence<'certificate, 'document, 'certificate, 'document>),
}
#[must_use]
pub fn member_source<'view, 'certificate, 'document>(
input: &'view MemberStatus<'view, 'certificate, 'document>,
) -> Option<StatusSource<'view, 'certificate, 'document>> {
match input {
MemberStatus::Crl(bound) => Some(StatusSource::Crl(bound.coverage())),
MemberStatus::CrlReading(bound) => Some(StatusSource::CrlReading(bound.reading())),
MemberStatus::Ocsp(bound) => Some(StatusSource::Ocsp(bound.evidence())),
MemberStatus::ResponderNoCheck(_) => None,
}
}
impl<'certificate, 'document> StatusSource<'_, 'certificate, 'document> {
#[must_use]
pub fn target(self) -> &'certificate structured_pkix::Certificate<'document> {
match self {
Self::Crl(qualified) => qualified.target(),
Self::CrlReading(reading) => reading.target(),
Self::Ocsp(qualified) => qualified.target(),
}
}
#[must_use]
pub fn evaluated_at(self) -> structured_pkix::temporal::Reading {
match self {
Self::Crl(qualified) => qualified.evaluated_at(),
Self::CrlReading(reading) => reading.evaluated_at(),
Self::Ocsp(qualified) => qualified.evaluated_at(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatusPart {
CompleteCrl,
DeltaCrl,
OcspResponse,
SigningCertificate,
}
#[derive(Debug)]
pub struct ArchivedStatus<N, D, G, P> {
pub source: usize,
pub part: StatusPart,
pub availability: Archived<N, D, G, P>,
}
pub struct StatusMaterial<'view, 'certificate, 'document, N, D, G, P> {
source: StatusSource<'view, 'certificate, 'document>,
archive: Location<N, D, G>,
entries: OwnedVec<ArchivedStatus<N, D, G, P>>,
all_covered: bool,
}
impl<'view, 'certificate, 'document, N: Copy, D: Copy, G: Copy, P>
StatusMaterial<'view, 'certificate, 'document, N, D, G, P>
{
#[must_use]
pub fn source(&self) -> StatusSource<'view, 'certificate, 'document> {
self.source
}
#[must_use]
pub fn archive(&self) -> Location<N, D, G> {
self.archive
}
#[must_use]
pub fn entries(&self) -> &[ArchivedStatus<N, D, G, P>] {
&self.entries
}
#[must_use]
pub fn all_artifacts_covered(&self) -> bool {
self.all_covered
}
}
pub fn archived<'view, 'certificate, 'document, N, D, G, P, O, W, Q, C, I>(
source: StatusSource<'view, 'certificate, 'document>,
index: &Index<'_, N, D, G, P, O, W, I>,
coverage: &Coverage<N, D, G, C, I>,
evidence: &Verified<N, D, G, I>,
query: &Q,
) -> Result<StatusMaterial<'view, 'certificate, 'document, N, D, G, P>, ArchiveError<N>>
where
N: Copy + Eq + HashWork,
D: Copy + Eq,
G: Copy + Eq,
P: Copy,
W: Work,
C: Work,
Q: Query<Id = N, Document = D, Generation = G>,
{
let signature = evidence.signature();
query
.admit(signature, 1)
.map_err(|cause| ArchiveError::Index(IndexError::Scope(cause)))?;
let mut entries = query.execution().vector();
let mut all_covered = true;
let mut record = |artifact, ordinal, part| {
let availability = index.archived(artifact, coverage, evidence, query)?;
if !matches!(availability, Archived::Present(_)) {
all_covered = false;
}
entries
.try_push(ArchivedStatus {
source: ordinal,
part,
availability,
})
.map_err(|cause| ArchiveError::Index(IndexError::Resource { signature, cause }))
};
visit_artifacts(source, signature, query, &mut record).map_err(|cause| match cause {
ArtifactVisitError::Scope(cause) => ArchiveError::Index(IndexError::Scope(cause)),
ArtifactVisitError::Visitor(cause) => cause,
})?;
Ok(StatusMaterial {
source,
archive: evidence.token(),
entries: entries.into_owned(),
all_covered,
})
}
fn record_crl<'certificate, 'document, E>(
source: &crl::EvidenceSource<'certificate, 'document>,
ordinal: usize,
mut record: impl FnMut(ArtifactRef<'certificate, 'document>, usize, StatusPart) -> Result<(), E>,
) -> Result<(), E> {
match source {
crl::EvidenceSource::Complete(complete) => {
record(
ArtifactRef::Crl(complete.crl()),
ordinal,
StatusPart::CompleteCrl,
)?;
record(
ArtifactRef::Certificate(complete.issuer()),
ordinal,
StatusPart::SigningCertificate,
)?;
}
crl::EvidenceSource::Delta { base, delta } => {
record(
ArtifactRef::Crl(base.crl()),
ordinal,
StatusPart::CompleteCrl,
)?;
record(ArtifactRef::Crl(delta.crl()), ordinal, StatusPart::DeltaCrl)?;
record(
ArtifactRef::Certificate(base.issuer()),
ordinal,
StatusPart::SigningCertificate,
)?;
record(
ArtifactRef::Certificate(delta.issuer()),
ordinal,
StatusPart::SigningCertificate,
)?;
}
}
Ok(())
}
#[derive(Debug)]
pub enum ArtifactVisitError<V, N> {
Scope(ScopeError<N>),
Visitor(V),
}
pub fn visit_artifacts<'certificate, 'document, Q: Query, V>(
source: StatusSource<'_, 'certificate, 'document>,
signature: Q::Id,
query: &Q,
mut record: impl FnMut(ArtifactRef<'certificate, 'document>, usize, StatusPart) -> Result<(), V>,
) -> Result<(), ArtifactVisitError<V, Q::Id>> {
let pay = || query.admit(signature, 2).map_err(ArtifactVisitError::Scope);
let record = |artifact, ordinal, part| {
record(artifact, ordinal, part).map_err(ArtifactVisitError::Visitor)
};
walk_artifacts(source, pay, record)
}
pub fn visit_artifacts_in<'c, 'd, W: Work>(
source: StatusSource<'_, 'c, 'd>,
work: &W,
record: impl FnMut(ArtifactRef<'c, 'd>, usize, StatusPart) -> Result<(), MemoryError>,
) -> Result<(), MemoryError> {
walk_artifacts(source, || work.charge_work(1), record)
}
fn walk_artifacts<'c, 'd, E>(
source: StatusSource<'_, 'c, 'd>,
mut pay: impl FnMut() -> Result<(), E>,
mut record: impl FnMut(ArtifactRef<'c, 'd>, usize, StatusPart) -> Result<(), E>,
) -> Result<(), E> {
match source {
StatusSource::Crl(qualified) => {
for (ordinal, entry) in qualified.sources().iter().enumerate() {
pay()?;
record_crl(&entry.source(), ordinal, &mut record)?;
}
}
StatusSource::CrlReading(reading) => {
pay()?;
record_crl(&reading.source(), 0, &mut record)?;
}
StatusSource::Ocsp(qualified) => {
pay()?;
let signed = qualified.evidence().source().signed();
record(
ArtifactRef::Ocsp(signed.response()),
0,
StatusPart::OcspResponse,
)?;
record(
ArtifactRef::Certificate(signed.signer()),
0,
StatusPart::SigningCertificate,
)?;
}
}
Ok(())
}