use super::{Interval, Purpose, TemporalError, Verified};
use crate::pki::{SignerReceipt, TimestampPath};
use crate::source::{Query, ScopeError};
use structured_pkix::temporal::LeapTable;
pub struct SignatureTime<'receipt, S, N, D, G, I> {
signer: &'receipt S,
timestamp: &'receipt Verified<N, D, G, I>,
generation: Interval,
}
impl<'receipt, S, N, D, G, I> SignatureTime<'receipt, S, N, D, G, I> {
#[must_use]
pub fn signer(&self) -> &'receipt S {
self.signer
}
#[must_use]
pub fn timestamp(&self) -> &'receipt Verified<N, D, G, I> {
self.timestamp
}
#[must_use]
pub fn generation(&self) -> Interval {
self.generation
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureTimeFailure {
Certificate,
Interval,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureTimeError<N> {
Scope(ScopeError<N>),
Foreign {
property: N,
},
Purpose {
property: N,
},
Time {
property: N,
cause: TemporalError,
},
}
pub enum SignatureTimeBinding<'receipt, S, N, D, G, I> {
Bound(SignatureTime<'receipt, S, N, D, G, I>),
Unproven(SignatureTimeFailure),
}
pub type SignatureTimeResult<'receipt, S, Q, I> = Result<
SignatureTimeBinding<
'receipt,
S,
<Q as Query>::Id,
<Q as Query>::Document,
<Q as Query>::Generation,
I,
>,
SignatureTimeError<<Q as Query>::Id>,
>;
pub fn signature_time<'receipt, 'path, 'document, S, Q, I>(
signer: &'receipt S,
timestamp: &'receipt Verified<Q::Id, Q::Document, Q::Generation, I>,
utc: &LeapTable<'_>,
query: &Q,
) -> SignatureTimeResult<'receipt, S, Q, I>
where
'document: 'path,
S: SignerReceipt<
'path,
'document,
Id = Q::Id,
Document = Q::Document,
Generation = Q::Generation,
>,
Q: Query,
{
bind(signer, timestamp, query, || {
timestamp.interval(utc, query.execution())
})
}
pub fn signature_time_path<'receipt, 'path, 'document, S, Q, I>(
signer: &'receipt S,
path: &'receipt TimestampPath<'_, '_, '_, Q::Id, Q::Document, Q::Generation, I>,
query: &Q,
) -> SignatureTimeResult<'receipt, S, Q, I>
where
'document: 'path,
S: SignerReceipt<
'path,
'document,
Id = Q::Id,
Document = Q::Document,
Generation = Q::Generation,
>,
Q: Query,
{
bind(signer, path.actor().verified(), query, || {
Ok(path.generation())
})
}
fn bind<'receipt, 'path, 'document, S, Q, I>(
signer: &'receipt S,
timestamp: &'receipt Verified<Q::Id, Q::Document, Q::Generation, I>,
query: &Q,
interval: impl FnOnce() -> Result<Interval, TemporalError>,
) -> SignatureTimeResult<'receipt, S, Q, I>
where
'document: 'path,
S: SignerReceipt<
'path,
'document,
Id = Q::Id,
Document = Q::Document,
Generation = Q::Generation,
>,
Q: Query,
{
let selected = signer.signature();
let property = timestamp.property();
if selected.snapshot != query.snapshot() || timestamp.token().snapshot != selected.snapshot {
return Err(SignatureTimeError::Foreign { property });
}
if !query
.equal(timestamp.signature(), selected.node)
.map_err(|cause| {
SignatureTimeError::Scope(ScopeError::Resource {
node: property,
cause,
})
})?
{
return Err(SignatureTimeError::Foreign { property });
}
query
.admit(selected.node, 1)
.map_err(SignatureTimeError::Scope)?;
if timestamp.purpose() != Purpose::Signature {
return Err(SignatureTimeError::Purpose { property });
}
let Some(path) = signer.path() else {
return Ok(SignatureTimeBinding::Unproven(
SignatureTimeFailure::Certificate,
));
};
let generation = interval().map_err(|cause| SignatureTimeError::Time { property, cause })?;
query
.admit(selected.node, 1)
.map_err(SignatureTimeError::Scope)?;
let checked = path.interval();
if checked.from_instant() > generation.earliest || checked.to_instant() < generation.latest {
return Ok(SignatureTimeBinding::Unproven(
SignatureTimeFailure::Interval,
));
}
Ok(SignatureTimeBinding::Bound(SignatureTime {
signer,
timestamp,
generation,
}))
}