use super::check::{self, Input, Mode};
use crate::{
binding::{Bound, Status},
certificate::{self, Agreement, DigestProvider},
digest,
produce::{Emit, Error, Transaction},
reference::ObjectLocation,
timestamp::{TimestampXml, XADES_141},
xml::{Access, Snapshot},
};
use structured_asn1::{
digest::Resolution,
memory::{OwnedVec, Work},
};
#[derive(Debug, Clone, Copy)]
pub struct Candidate<N> {
pub reference: N,
pub location: ObjectLocation,
pub status: Status,
}
#[derive(Debug)]
pub enum CreationError<E, PE, N, D, G> {
Input(crate::timestamp::InputError<E, N>),
Processing(super::CheckError<E, PE, N, D, G>),
Algorithm {
method: N,
answer: Agreement,
},
Ineligible {
reference: N,
},
Unchanged {
reference: N,
},
}
struct Pair<N, O> {
reference: N,
instruction: O,
object: O,
}
pub struct Renewal<N, D, G, O> {
snapshot: Snapshot<D, G>,
signature: N,
parent: Option<N>,
canonicalization: N,
digest: N,
pairs: OwnedVec<Pair<N, O>>,
}
impl<N: Copy, D: Copy + Eq, G: Copy + Eq, O: AsRef<[u8]>> Renewal<N, D, G, O> {
#[must_use]
pub fn len(&self) -> usize {
self.pairs.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
pub fn write<B: Transaction<Id = N, Document = D, Generation = G>, W: Work>(
&self,
writer: &mut B,
work: &W,
) -> Result<(), Error<B::Error>>
where
N: structured_asn1::memory::HashWork,
{
work.charge_work(1).map_err(Error::Resource)?;
if writer.base() != self.snapshot
|| !structured_ltv::source::equal_id(writer.signature(), self.signature, work)
.map_err(Error::Resource)?
|| !crate::produce::same_parent(writer.unsigned_parent(), self.parent, work)
.map_err(Error::Resource)?
{
return Err(Error::Foreign);
}
if self.pairs.is_empty() {
return Ok(());
}
let mut emit = Emit { writer, work };
emit.begin(XADES_141, "RenewedDigestsV2")?;
work.charge_work(1).map_err(Error::Resource)?;
emit.writer
.method(self.canonicalization, work)
.map_err(Error::Writer)?;
work.charge_work(1).map_err(Error::Resource)?;
emit.writer
.method(self.digest, work)
.map_err(Error::Writer)?;
for pair in &self.pairs {
emit.begin(XADES_141, "RecomputedDigestValue")?;
emit.binary(XADES_141, "NewSDODigestValue", pair.object.as_ref())?;
emit.binary(XADES_141, "OriginalRefDigest", pair.instruction.as_ref())?;
emit.end()?;
}
emit.end()?;
work.charge_work(1).map_err(Error::Resource)?;
if emit.writer.base() != self.snapshot
|| !structured_ltv::source::equal_id(emit.writer.signature(), self.signature, work)
.map_err(Error::Resource)?
|| !crate::produce::same_parent(emit.writer.unsigned_parent(), self.parent, work)
.map_err(Error::Resource)?
{
return Err(Error::Foreign);
}
Ok(())
}
pub fn references(&self) -> impl ExactSizeIterator<Item = N> + '_ {
self.pairs.iter().map(|p| p.reference)
}
}
pub type CreationResult<T, X, PE> = Result<
T,
CreationError<
<X as crate::xml::Xml>::Error,
PE,
<X as crate::xml::Xml>::Id,
<X as crate::xml::Xml>::Document,
<X as crate::xml::Xml>::Generation,
>,
>;
pub type Created<'p, X, P> = Renewal<
<X as crate::xml::Xml>::Id,
<X as crate::xml::Xml>::Document,
<X as crate::xml::Xml>::Generation,
<P as structured_asn1::digest::Digest<'p>>::Output,
>;
pub fn create<'p, X, P, W>(
xml: &X,
bound: &Bound<X::Id, X::Document, X::Generation>,
canonicalization: X::Id,
new_method: X::Id,
provider: &'p P,
work: &W,
selected: impl FnMut(Candidate<X::Id>, &W) -> Result<bool, P::Error>,
) -> CreationResult<Created<'p, X, P>, X, P::Error>
where
X: TimestampXml,
P: DigestProvider<'p, X>,
W: Work,
{
let layout = bound.containers();
let access = Access::new(xml, layout.snapshot(), work);
let processing = CreationError::Processing;
let resource = |cause| {
processing(super::CheckError::Resource {
node: new_method,
cause,
})
};
let parent = match layout.unsigned() {
Some(unsigned) => crate::timestamp::optional(
&access,
unsigned,
crate::container::XADES,
"UnsignedSignatureProperties",
)
.map_err(CreationError::Input)?,
None => None,
};
access
.query(layout.signature(), |_, _, _| Ok(()))
.map_err(|e| processing(super::CheckError::Access(e)))?;
if !access
.has_name(
canonicalization,
crate::xml::Name {
namespace: crate::container::DS,
local: "CanonicalizationMethod",
},
)
.map_err(|e| processing(super::CheckError::Access(e)))?
{
return Err(CreationError::Ineligible {
reference: canonicalization,
});
}
if let Some(answer) = certificate::preflight_method(&access, new_method)
.map_err(|e| processing(super::CheckError::Access(e)))?
{
return Err(CreationError::Algorithm {
method: new_method,
answer,
});
}
let identity = match check::resolve(&access, new_method, provider).map_err(processing)? {
certificate::Expected::Ready(identity) => identity,
certificate::Expected::Answer(answer) => {
return Err(CreationError::Algorithm {
method: new_method,
answer,
});
}
};
let choices = choose(
&access,
layout.signature(),
new_method,
&identity,
provider,
selected,
)?;
let mut pairs = work.vector();
pairs.try_reserve_exact(choices.len()).map_err(resource)?;
for reference in choices {
let mut source = Input {
xml,
snapshot: layout.snapshot(),
mode: Mode::Canonical {
node: reference,
method: canonicalization,
},
};
let instruction = computed(&identity, &mut source, provider, work, reference)?;
source.mode = Mode::Reference {
signature: layout.signature(),
node: reference,
};
let object = computed(&identity, &mut source, provider, work, reference)?;
pairs
.try_push(Pair {
reference,
instruction,
object,
})
.map_err(resource)?;
}
access
.query(layout.signature(), |_, _, _| Ok(()))
.map_err(|e| processing(super::CheckError::Access(e)))?;
Ok(Renewal {
snapshot: layout.snapshot(),
signature: layout.signature(),
parent,
canonicalization,
digest: new_method,
pairs: pairs.into_owned(),
})
}
fn choose<'p, X: TimestampXml, P: DigestProvider<'p, X>, W: Work>(
access: &Access<'_, X, W>,
signature: X::Id,
new_method: X::Id,
identity: &certificate::FormatDigest<P::Identity>,
provider: &P,
mut selected: impl FnMut(Candidate<X::Id>, &W) -> Result<bool, P::Error>,
) -> CreationResult<OwnedVec<X::Id>, X, P::Error> {
let work = access.execution();
let processing = CreationError::Processing;
let resource = |cause| {
processing(super::CheckError::Resource {
node: new_method,
cause,
})
};
let instructions = check::inventory(access, signature, new_method).map_err(processing)?;
let mut choices = work.vector();
for instruction in instructions {
work.charge_work(1).map_err(resource)?;
let choose = selected(
Candidate {
reference: instruction.node,
location: instruction.location,
status: instruction.status,
},
work,
)
.map_err(|cause| {
processing(super::CheckError::Provider {
node: instruction.node,
cause,
})
})?;
if !choose {
continue;
}
if instruction.location != ObjectLocation::Detached || instruction.status != Status::Valid {
return Err(CreationError::Ineligible {
reference: instruction.node,
});
}
let method = check::original_method(access, instruction.node)
.map_err(|e| processing(super::CheckError::Property(e)))?;
let old = match check::resolve(access, method, provider).map_err(processing)? {
certificate::Expected::Ready(identity) => identity,
certificate::Expected::Answer(answer) => {
return Err(CreationError::Algorithm { method, answer });
}
};
work.charge_work(1).map_err(resource)?;
if provider
.compare(old.identity(), identity.identity(), work)
.map_err(|cause| {
processing(super::CheckError::Provider {
node: method,
cause,
})
})?
== core::cmp::Ordering::Equal
{
return Err(CreationError::Unchanged {
reference: instruction.node,
});
}
choices.try_push(instruction.node).map_err(resource)?;
}
Ok(choices.into_owned())
}
fn computed<'p, X: TimestampXml, P: DigestProvider<'p, X>, W: Work>(
identity: &certificate::FormatDigest<P::Identity>,
source: &mut Input<'_, X>,
provider: &'p P,
work: &W,
node: X::Id,
) -> CreationResult<P::Output, X, P::Error> {
match digest::compute_resolved::<P, _, _>(identity, source, provider, work)
.map_err(|cause| CreationError::Processing(super::CheckError::Digest { node, cause }))?
{
Resolution::Supported(output) => Ok(output),
Resolution::Unsupported => Err(CreationError::Algorithm {
method: node,
answer: Agreement::Unsupported,
}),
Resolution::Malformed => Err(CreationError::Algorithm {
method: node,
answer: Agreement::MalformedMethod,
}),
}
}