use super::*;
use structured_asn1::memory::{MemoryError, OwnedMap, OwnedText, OwnedVec, Work};
mod hash_uses;
pub use hash_uses::*;
mod key_receipt;
pub use key_receipt::*;
type RetainedOperation<'scope, 'inputs, W> = CapturedOperation<
&'scope crate::policy::VerificationPolicy,
&'scope VerificationOperationBudgets<'inputs>,
ScopedCapture<'scope, W>,
>;
pub struct VerifiedSession<'view, 'scope, 'inputs, W: Work> {
view: DocumentView<'view>,
context: &'scope VerifyContext<'inputs>,
budgets: &'scope VerificationOperationBudgets<'inputs>,
evidence: VerifyEvidence<OwnedVec<SignatureEvidence, W>>,
operations: OwnedVec<RetainedOperation<'scope, 'inputs, W>, W>,
resolver: UriReferenceResolver<'view>,
work: &'scope W,
replay_active: Cell<bool>,
}
pub fn with_verified_request<'inputs, W: Work, R>(
document: &XmlDocument,
context: &VerifyContext<'inputs>,
request: &VerificationRequest<'inputs>,
work: &W,
continuation: impl for<'view, 'scope> FnOnce(
VerifiedSession<'view, 'scope, 'inputs, W>,
) -> Result<R, DsigError>,
) -> Result<R, DsigError> {
with_verified_document(
document,
context,
request,
ScopedRequestStorage(work),
|view, context, budgets, evidence, operations, resolver| {
continuation(VerifiedSession {
view,
context,
budgets,
evidence,
operations,
resolver,
work,
replay_active: Cell::new(false),
})
},
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReferenceParent {
SignedInfo(NodeIdentity),
Manifest(NodeIdentity),
}
#[derive(Debug)]
pub struct AuthenticatedReference<'a> {
pub identity: NodeIdentity,
pub parent: ReferenceParent,
pub uri: Option<&'a str>,
pub reference_type: Option<&'a str>,
pub result: Option<&'a ReferenceResult>,
pub location: ReferenceLocation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReferenceLocation {
SameDocument(NodeIdentity),
Detached(Option<[u8; 32]>),
Unchecked,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CatalogueCompleteness {
Complete,
Unchecked(NodeIdentity),
Incomplete(NodeIdentity),
}
#[derive(Debug)]
pub enum CatalogueError<E> {
Processing(DsigError),
Visitor(E),
}
fn flatten_catalogue<T, E>(
result: Result<T, CatalogueError<CatalogueError<E>>>,
) -> Result<T, CatalogueError<E>> {
match result {
Ok(value) => Ok(value),
Err(CatalogueError::Processing(error)) => Err(CatalogueError::Processing(error)),
Err(CatalogueError::Visitor(error)) => Err(error),
}
}
#[derive(Clone, Copy, Debug)]
pub enum LexicalSource<'a> {
Attribute {
namespace: Option<&'a str>,
local_name: &'a str,
},
SimpleContent,
}
#[derive(Debug)]
pub enum ReferenceResolution<'a> {
Absent,
Malformed,
Unsupported,
Detached,
Unresolved,
NotReference(NodeIdentity),
Unauthenticated(NodeIdentity),
Reference(AuthenticatedReference<'a>),
}
enum LexicalValue<'a, W: Work> {
Borrowed(&'a str),
Joined(OwnedText<W>),
}
impl<W: Work> LexicalValue<'_, W> {
fn as_str(&self) -> &str {
match self {
Self::Borrowed(value) => value,
Self::Joined(value) => value.as_str(),
}
}
}
pub struct SelectedSignature<'session, 'view, 'scope, 'inputs, W: Work, Q: Work = W> {
session: &'session VerifiedSession<'view, 'scope, 'inputs, W>,
index: usize,
work: &'session Q,
results: OwnedMap<NodeIdentity, (ReferenceSet, usize), Q>,
replays: RefCell<OwnedVec<ReplayRecord, Q>>,
}
impl<'view, 'scope, 'inputs, W: Work> VerifiedSession<'view, 'scope, 'inputs, W> {
pub fn view(&self) -> DocumentView<'view> {
self.view
}
pub fn evidence(&self) -> &VerifyEvidence<OwnedVec<SignatureEvidence, W>> {
&self.evidence
}
pub fn into_evidence(self) -> VerifyEvidence<OwnedVec<SignatureEvidence, W>> {
self.evidence
}
pub fn select_signature(
&self,
identity: NodeIdentity,
) -> Result<SelectedSignature<'_, 'view, 'scope, 'inputs, W>, DsigError> {
self.select_signature_in(identity, self.work)
}
pub fn with_query<Q: Work, R, E>(
&self,
identity: NodeIdentity,
work: &Q,
visitor: impl for<'query> FnOnce(
SelectedSignature<'query, 'view, 'scope, 'inputs, W, Q>,
) -> Result<R, E>,
) -> Result<R, CatalogueError<E>> {
let selected = self
.select_signature_in(identity, work)
.map_err(CatalogueError::Processing)?;
visitor(selected).map_err(CatalogueError::Visitor)
}
fn select_signature_in<'query, Q: Work>(
&'query self,
identity: NodeIdentity,
work: &'query Q,
) -> Result<SelectedSignature<'query, 'view, 'scope, 'inputs, W, Q>, DsigError> {
let index = self.authenticated_signature_in(identity, work)?;
let entry = &self.evidence.signatures()[index];
let mut results = work.map();
if let Ok(result) = &entry.result {
for (set, entries) in [
(ReferenceSet::SignedInfo, &result.signed_info_references),
(ReferenceSet::Manifest, &result.manifest_references),
] {
for (ordinal, reference) in entries.iter().enumerate() {
work.charge_work(1)?;
if let Some(node) = reference.reference_identity
&& results.try_insert(node, (set, ordinal))?.is_some()
{
return Err(DsigError::InvalidRequest {
reason: "ambiguous original Reference evidence",
});
}
}
}
}
Ok(SelectedSignature {
session: self,
index,
work,
results,
replays: RefCell::new(work.vector()),
})
}
fn authenticated_signature_in<Q: Work>(
&self,
identity: NodeIdentity,
work: &Q,
) -> Result<usize, DsigError> {
work.charge_work(1)?;
self.view.resolve_node(identity)?;
for (index, entry) in self.evidence.signatures().iter().enumerate() {
work.charge_work(1)?;
if entry.identity != identity {
continue;
}
let retained = &self.operations[index];
if let Some(error) = retained.capture.failure.get() {
return Err(error.into());
}
let operation = &retained.operation;
operation.validate_document_view(self.view)?;
work.charge_work(
u64::try_from(operation.decision_count()).map_err(|_| MemoryError::WorkExceeded)?,
)?;
if operation.accepted_signature().is_none() {
return Err(DsigError::InvalidRequest {
reason: "selected Signature is not authenticated",
});
}
return Ok(index);
}
Err(DsigError::InvalidRequest {
reason: "Signature is not part of this request",
})
}
fn signature_method_in<Q: Work>(
&self,
index: usize,
work: &Q,
) -> Result<SelectedSignatureMethod, DsigError> {
work.charge_work(1)?;
let retained = &self.operations[index];
retained.operation.validate_document_view(self.view)?;
if let Some(error) = retained.capture.failure.get() {
return Err(error.into());
}
retained
.capture
.method
.get()
.ok_or(DsigError::InvalidRequest {
reason: "selected Signature has no original method receipt",
})
}
}
impl<'session, 'view, 'scope, 'inputs, W: Work, Q: Work>
SelectedSignature<'session, 'view, 'scope, 'inputs, W, Q>
{
fn operation(
&self,
) -> &OperationExecutionContext<
&'scope crate::policy::VerificationPolicy,
&'scope VerificationOperationBudgets<'inputs>,
> {
&self.session.operations[self.index].operation
}
fn signature(&self) -> Result<Node<'_, '_>, DsigError> {
Ok(self
.session
.view
.resolve_node(self.session.evidence.signatures()[self.index].identity)?)
}
fn authenticates_element(&self, node: Node<'_, '_>) -> Result<bool, DsigError> {
let signature = self.signature()?;
for ancestor in node.ancestors() {
self.work.charge_work(1)?;
if ancestor.has_tag_name((XMLDSIG_NS, "SignedInfo"))
&& ancestor.parent() == Some(signature)
{
return Ok(true);
}
if self
.operation()
.is_authenticated(self.session.view.node_identity(ancestor))
{
return Ok(true);
}
}
Ok(false)
}
pub fn resolve_reference(
&self,
property: NodeIdentity,
source: LexicalSource<'_>,
) -> Result<ReferenceResolution<'_>, DsigError> {
self.work.charge_work(1)?;
self.operation().validate_document_view(self.session.view)?;
let node = self.session.view.resolve_node(property)?;
if !node.is_element() {
return Err(DsigError::InvalidRequest {
reason: "reference property must be an element",
});
}
if !self.authenticates_element(node)? {
return Ok(ReferenceResolution::Unauthenticated(property));
}
let value = match source {
LexicalSource::Attribute {
namespace,
local_name,
} => {
let mut value = None;
for attribute in node.attributes() {
let bytes = attribute
.name()
.len()
.checked_add(attribute.namespace().map_or(0, str::len))
.and_then(|n| n.checked_add(1))
.ok_or(MemoryError::WorkExceeded)?;
self.work.charge_work(
u64::try_from(bytes).map_err(|_| MemoryError::WorkExceeded)?,
)?;
if attribute.name() == local_name && attribute.namespace() == namespace {
value = Some(attribute.value());
break;
}
}
let Some(value) = value else {
return Ok(ReferenceResolution::Absent);
};
LexicalValue::Borrowed(value)
}
LexicalSource::SimpleContent => {
let mut value = LexicalValue::Borrowed("");
for child in node.children() {
self.work.charge_work(1)?;
if child.is_element() {
return Ok(ReferenceResolution::Malformed);
}
let Some(text) = child.text().filter(|_| child.is_text()) else {
continue;
};
match &mut value {
LexicalValue::Borrowed(first) if first.is_empty() => *first = text,
LexicalValue::Borrowed(first) => {
let mut joined = OwnedText::try_from_str(first, self.work)?;
joined.try_push_str(text)?;
value = LexicalValue::Joined(joined);
}
LexicalValue::Joined(joined) => joined.try_push_str(text)?,
}
}
value
}
};
let uri = value.as_str();
self.work
.charge_work(u64::try_from(uri.len()).map_err(|_| MemoryError::WorkExceeded)?)?;
if !self.session.context.policy.uris.references.allows(uri) {
return Err(crate::policy::PolicyViolation::Uri {
operation: "extension reference resolution",
reason: "reference URI class is not permitted",
}
.into());
}
if !uri.is_empty() && !uri.starts_with('#') {
crate::c14n::xml_base::resolve_uri_from_node_in(
node,
uri,
None,
self.session.budgets.transforms.xml_base_resolution(),
&crate::c14n::scratch::Accounted(self.work),
)
.map_err(super::super::uri::map_xml_base_execution_error)
.map_err(|error| {
DsigError::Reference(ReferenceProcessingError::UriDereference(error))
})?;
return Ok(ReferenceResolution::Detached);
}
let target = self.session.resolver.node_for_same_document_reference_in(
uri,
Some(self.session.budgets.transforms.node_set_materialization()),
&crate::c14n::scratch::Accounted(self.work),
);
let target = match target {
Ok(target) => target,
Err(
TransformError::UnsupportedUri(_)
| TransformError::AccountedUnsupportedUri(_)
| TransformError::InvalidFragment,
) => return Ok(ReferenceResolution::Unsupported),
Err(error) => {
return Err(DsigError::Reference(
ReferenceProcessingError::UriDereference(error),
));
}
};
let Some(target) = target else {
return Ok(ReferenceResolution::Unresolved);
};
let identity = self.session.view.node_identity(target);
if !target.has_tag_name((XMLDSIG_NS, "Reference")) {
return Ok(ReferenceResolution::NotReference(identity));
}
match self.descriptor(identity)? {
Some(reference) => Ok(ReferenceResolution::Reference(reference)),
None => Ok(ReferenceResolution::Unauthenticated(identity)),
}
}
fn parent(&self, reference: Node<'_, '_>) -> Result<Option<ReferenceParent>, DsigError> {
if !reference.has_tag_name((XMLDSIG_NS, "Reference")) {
return Ok(None);
}
let Some(parent) = reference.parent() else {
return Ok(None);
};
let signature = self.signature()?;
let view = self.session.view;
if parent.has_tag_name((XMLDSIG_NS, "SignedInfo")) && parent.parent() == Some(signature) {
return Ok(Some(ReferenceParent::SignedInfo(
view.node_identity(parent),
)));
}
if !parent.has_tag_name((XMLDSIG_NS, "Manifest")) {
return Ok(None);
}
for ancestor in parent.ancestors() {
self.work.charge_work(1)?;
if self
.operation()
.is_authenticated(view.node_identity(ancestor))
{
return Ok(Some(ReferenceParent::Manifest(view.node_identity(parent))));
}
}
Ok(None)
}
fn descriptor(
&self,
reference: NodeIdentity,
) -> Result<Option<AuthenticatedReference<'_>>, DsigError> {
self.work.charge_work(1)?;
self.operation().validate_document_view(self.session.view)?;
let node = self.session.view.resolve_node(reference)?;
let Some(parent) = self.parent(node)? else {
return Ok(None);
};
self.descriptor_in_parent(node, parent).map(Some)
}
fn descriptor_in_parent<'a>(
&'a self,
node: Node<'a, 'a>,
parent: ReferenceParent,
) -> Result<AuthenticatedReference<'a>, DsigError> {
let reference = self.session.view.node_identity(node);
let result = match self.results.get(&reference, self.work)? {
Some((set, index)) => self.session.evidence.signatures()[self.index]
.result
.as_ref()
.ok()
.map(|result| match set {
ReferenceSet::SignedInfo => &result.signed_info_references[*index],
ReferenceSet::Manifest => &result.manifest_references[*index],
}),
None => None,
};
for _ in node.attributes() {
self.work.charge_work(9)?;
}
Ok(AuthenticatedReference {
identity: reference,
parent,
uri: node.attribute("URI"),
reference_type: node.attribute("Type"),
result,
location: if let Some(target) = result.and_then(|result| result.target_identity) {
ReferenceLocation::SameDocument(target)
} else {
match self
.session
.budgets
.resolved_reference_resources
.borrow()
.get(&node.id())
{
Some(OperationResourceIdentity::External { fingerprint, .. }) => {
ReferenceLocation::Detached(Some(*fingerprint))
}
Some(OperationResourceIdentity::MissingExternal) => {
ReferenceLocation::Detached(None)
}
_ => ReferenceLocation::Unchecked,
}
},
})
}
pub fn visit_authenticated_references<E>(
&self,
mut visitor: impl for<'a> FnMut(AuthenticatedReference<'a>) -> Result<(), E>,
) -> Result<CatalogueCompleteness, CatalogueError<E>> {
flatten_catalogue(self.visit_reference_instructions(|node, parent, _| {
let reference = self
.descriptor_in_parent(node, parent)
.map_err(CatalogueError::Processing)?;
visitor(reference).map_err(CatalogueError::Visitor)
}))
}
fn visit_reference_instructions<E>(
&self,
mut visitor: impl for<'a> FnMut(
Node<'a, 'a>,
ReferenceParent,
Option<Node<'a, 'a>>,
) -> Result<(), E>,
) -> Result<CatalogueCompleteness, CatalogueError<E>> {
let mut completeness = CatalogueCompleteness::Complete;
let mut references = 0;
let maximum = self.session.context.policy.resources.max_references;
self.operation()
.validate_document_view(self.session.view)
.map_err(|error| CatalogueError::Processing(error.into()))?;
for node in self.session.view.document().descendants() {
self.work
.charge_work(1)
.map_err(|error| CatalogueError::Processing(error.into()))?;
if node.has_tag_name((XMLDSIG_NS, "Manifest"))
&& self
.authenticates_element(node)
.map_err(CatalogueError::Processing)?
{
let mut count = 0;
let mut malformed = false;
for child in node.children() {
self.work
.charge_work(1)
.map_err(|error| CatalogueError::Processing(error.into()))?;
if child.has_tag_name((XMLDSIG_NS, "Reference")) {
count += 1;
} else if child.is_element() {
malformed = true;
} else if child.is_text() {
let text = child.text().unwrap_or_default();
self.work
.charge_work(text.len() as u64)
.map_err(|error| CatalogueError::Processing(error.into()))?;
if !is_xml_whitespace_only(text) {
malformed = true;
}
}
}
if (malformed || count == 0)
&& !matches!(completeness, CatalogueCompleteness::Incomplete(_))
{
completeness =
CatalogueCompleteness::Incomplete(self.session.view.node_identity(node));
}
}
if !node.has_tag_name((XMLDSIG_NS, "Reference")) {
continue;
}
let Some(parent) = self.parent(node).map_err(CatalogueError::Processing)? else {
continue;
};
let identity = self.session.view.node_identity(node);
if references == maximum {
return Err(CatalogueError::Processing(
crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
maximum,
actual: references + 1,
}
.into(),
));
}
references += 1;
self.work
.charge_work(128)
.map_err(|error| CatalogueError::Processing(error.into()))?;
for child in node.children() {
let text = child.text().filter(|_| child.is_text()).map_or(0, str::len);
let cost = u64::try_from(text)
.ok()
.and_then(|length| length.checked_add(128))
.ok_or_else(|| CatalogueError::Processing(MemoryError::WorkExceeded.into()))?;
self.work
.charge_work(cost)
.map_err(|error| CatalogueError::Processing(error.into()))?;
}
let method = super::super::parse::inspect_reference_layout(node)
.ok()
.map(|(_, method, _)| method);
if method.is_none() {
if !matches!(completeness, CatalogueCompleteness::Incomplete(_)) {
completeness = CatalogueCompleteness::Incomplete(identity);
}
} else if let ReferenceParent::Manifest(parent) = parent
&& matches!(completeness, CatalogueCompleteness::Complete)
&& self.session.context.policy.manifest_processing
== crate::policy::ManifestProcessing::Ignore
{
completeness = CatalogueCompleteness::Unchecked(parent);
}
visitor(node, parent, method).map_err(CatalogueError::Visitor)?;
}
Ok(completeness)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReplayMode {
ReferenceOutput,
Timestamp(NodeIdentity),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReplayOutcome {
Complete,
Unavailable(ExternalResourceUnavailable),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ExternalResourceUnavailable {
pub reference: NodeIdentity,
}
#[derive(Debug)]
pub enum ReplayError<E> {
Processing(DsigError),
Receiver(E),
}
impl<E> From<TransformError> for ReplayError<E> {
fn from(error: TransformError) -> Self {
Self::Processing(match error {
TransformError::C14n(crate::c14n::C14nError::SharedExecution(error)) => error.into(),
error => ReferenceProcessingError::Transform(error).into(),
})
}
}
impl<E> From<OperationPlanError> for ReplayError<E> {
fn from(error: OperationPlanError) -> Self {
Self::Processing(error.into())
}
}
struct ReplayWriter<'a, W: Work, E, F> {
work: &'a W,
budget: &'a TransformExecutionBudget,
canonicalizing: bool,
receiver: &'a mut F,
failure: Option<ReplayError<E>>,
}
struct ReplayTerminal<'a, W: Work, E, F> {
work: &'a W,
receiver: &'a mut F,
timestamp: Option<(crate::c14n::C14nAlgorithm, Option<&'a str>)>,
error: std::marker::PhantomData<fn() -> E>,
}
impl<'a, W: Work, E, F: FnMut(&[u8]) -> Result<(), E>>
super::super::transforms::TransformTerminal<crate::c14n::scratch::Accounted<'a, W>>
for ReplayTerminal<'a, W, E, F>
{
type Output = ();
type Error = ReplayError<E>;
fn finish<D: crate::xml::dom::storage::Storage>(
self,
data: super::super::transforms::ExecutionData<
'_,
crate::c14n::scratch::Accounted<'a, W>,
D,
>,
explicit: Option<(&crate::c14n::C14nAlgorithm, Option<&str>)>,
budget: &TransformExecutionBudget,
) -> Result<(), ReplayError<E>> {
let mut output = ReplayWriter {
work: self.work,
budget,
canonicalizing: data.nodes().is_some(),
receiver: self.receiver,
failure: None,
};
let result = if let Some(bytes) = data.binary() {
use std::io::Write;
output.write_all(bytes).map_err(crate::c14n::C14nError::Io)
} else {
let nodes = data.nodes().expect("execution has nodes or bytes");
let implicit = crate::c14n::C14nAlgorithm::from_uri(DEFAULT_IMPLICIT_C14N_URI)
.expect("constant C14N URI");
let algorithm = explicit
.map(|(algorithm, _)| algorithm)
.or(self.timestamp.as_ref().map(|(algorithm, _)| algorithm))
.unwrap_or(&implicit);
let prefixes = if explicit.is_none() {
self.timestamp.as_ref().and_then(|(_, prefixes)| *prefixes)
} else {
explicit.and_then(|(_, prefixes)| prefixes)
};
crate::c14n::canonicalize_visible_to_writer_accounted(
nodes.document(),
Some(nodes),
algorithm,
super::super::transforms::replay_output_limit(budget),
budget.xml_base_resolution(),
&mut output,
self.work,
prefixes,
)
};
if let Some(error) = output.failure {
return Err(error);
}
result.map_err(|error| match error {
crate::c14n::C14nError::SharedExecution(error) => ReplayError::Processing(error.into()),
error => super::super::transforms::replay_c14n_error(error, budget).into(),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ReplayState {
Pending,
Complete,
Unavailable,
Rejected,
}
struct ReplayRecord {
signature: NodeIdentity,
reference: NodeIdentity,
state: ReplayState,
}
struct AdmittedReplay<'a, W: Work> {
records: &'a RefCell<OwnedVec<ReplayRecord, W>>,
active: &'a Cell<bool>,
index: usize,
}
impl<W: Work> AdmittedReplay<'_, W> {
fn finish<E>(&self, result: &Result<ReplayOutcome, ReplayError<E>>) {
self.records.borrow_mut()[self.index].state = match result {
Ok(ReplayOutcome::Complete) => ReplayState::Complete,
Ok(ReplayOutcome::Unavailable(_)) => ReplayState::Unavailable,
Err(_) => ReplayState::Rejected,
};
}
}
impl<W: Work> Drop for AdmittedReplay<'_, W> {
fn drop(&mut self) {
let mut records = self.records.borrow_mut();
if records[self.index].state == ReplayState::Pending {
records[self.index].state = ReplayState::Rejected;
}
self.active.set(false);
}
}
impl<W: Work, E, F: FnMut(&[u8]) -> Result<(), E>> std::io::Write for ReplayWriter<'_, W, E, F> {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
if self.failure.is_some() {
return Err(std::io::ErrorKind::Other.into());
}
let result = (|| {
let units = u64::try_from(bytes.len())
.map_err(|_| ReplayError::Processing(MemoryError::WorkExceeded.into()))?;
self.work
.charge_work(units)
.map_err(|error| ReplayError::Processing(error.into()))?;
if self.canonicalizing {
super::super::transforms::charge_replay_output(self.budget, bytes.len())?;
}
(self.receiver)(bytes).map_err(ReplayError::Receiver)
})();
match result {
Ok(()) => Ok(bytes.len()),
Err(error) => {
self.failure = Some(error);
Err(std::io::ErrorKind::Other.into())
}
}
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<W: Work, Q: Work> SelectedSignature<'_, '_, '_, '_, W, Q> {
pub fn replay_reference<E>(
&self,
reference: NodeIdentity,
mode: ReplayMode,
receiver: &mut impl FnMut(&[u8]) -> Result<(), E>,
) -> Result<ReplayOutcome, ReplayError<E>> {
self.work
.charge_work(
u64::try_from(self.operation().decision_count())
.map_err(|_| ReplayError::Processing(MemoryError::WorkExceeded.into()))?,
)
.map_err(|error| ReplayError::Processing(error.into()))?;
self.operation()
.run_reference_replay(self.session.view, reference, || {
self.replay_admitted(reference, mode, receiver)
})
}
fn replay_admitted<E>(
&self,
reference: NodeIdentity,
mode: ReplayMode,
receiver: &mut impl FnMut(&[u8]) -> Result<(), E>,
) -> Result<ReplayOutcome, ReplayError<E>> {
let processing = ReplayError::Processing;
let descriptor = self
.descriptor(reference)
.map_err(processing)?
.ok_or_else(|| {
processing(DsigError::InvalidRequest {
reason: "Reference instruction is not authenticated by selected Signature",
})
})?;
if matches!(descriptor.parent, ReferenceParent::Manifest(_))
&& self.session.context.policy.manifest_processing
== crate::policy::ManifestProcessing::Ignore
{
return Err(processing(
crate::policy::PolicyViolation::ManifestProcessingDisabled.into(),
));
}
if self.session.replay_active.get() {
return Err(processing(DsigError::InvalidRequest {
reason: "reentrant replay would reset the active transform chain",
}));
}
let index = {
let mut records = self.replays.borrow_mut();
let index = records.len();
records
.try_push(ReplayRecord {
signature: self.session.evidence.signatures()[self.index].identity,
reference,
state: ReplayState::Pending,
})
.map_err(|error| processing(error.into()))?;
debug_assert_eq!(records[index].reference, reference);
debug_assert_eq!(
records[index].signature,
self.session.evidence.signatures()[self.index].identity
);
index
};
self.session.replay_active.set(true);
let admission = AdmittedReplay {
records: &self.replays,
active: &self.session.replay_active,
index,
};
let result = self.replay_instruction(descriptor, mode, receiver);
admission.finish(&result);
result
}
fn replay_instruction<E>(
&self,
descriptor: AuthenticatedReference<'_>,
mode: ReplayMode,
receiver: &mut impl FnMut(&[u8]) -> Result<(), E>,
) -> Result<ReplayOutcome, ReplayError<E>> {
let processing = ReplayError::Processing;
let node = self
.session
.view
.resolve_node(descriptor.identity)
.map_err(|error| processing(error.into()))?;
let uri = descriptor.uri.ok_or_else(|| {
processing(DsigError::Reference(ReferenceProcessingError::MissingUri))
})?;
if !self.session.context.policy.uris.references.allows(uri) {
return Err(processing(
crate::policy::PolicyViolation::Uri {
operation: "reference replay",
reason: "reference URI class is not permitted",
}
.into(),
));
}
let allowed = self
.session
.context
.policy
.transforms
.allowed_algorithms
.as_ref();
let maximum = self
.session
.context
.policy
.resources
.max_transforms_per_reference;
let mut transform_count = 0;
self.work
.charge_work(node.children().len() as u64)
.map_err(|error| processing(error.into()))?;
for transforms in node
.children()
.filter(|child| child.has_tag_name((XMLDSIG_NS, "Transforms")))
{
self.work
.charge_work(transforms.children().len() as u64)
.map_err(|error| processing(error.into()))?;
for transform in transforms.children().filter(Node::is_element) {
self.work
.charge_work(1)
.map_err(|error| processing(error.into()))?;
transform_count += 1;
if transform_count > maximum {
return Err(processing(
crate::policy::PolicyViolation::ResourceLimit {
resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
maximum,
actual: transform_count,
}
.into(),
));
}
self.work
.charge_work(transform.attributes().len() as u64)
.map_err(|error| processing(error.into()))?;
if let Some(algorithm) = transform.attribute("Algorithm") {
self.work
.charge_work(algorithm.len() as u64)
.map_err(|error| processing(error.into()))?;
enforce_current_transform_allowed(allowed, algorithm, self.work)
.map_err(processing)?;
}
}
}
let parse_failure = |error| match error {
super::super::parse::ParseError::Transform(TransformError::C14n(
crate::c14n::C14nError::SharedExecution(error),
)) => processing(error.into()),
error => processing(DsigError::ParseManifestReference(error)),
};
let transforms = super::super::parse::parse_replay_transforms(
node,
&mut self.session.budgets.xpath_parse.borrow_mut(),
&crate::c14n::scratch::Accounted(self.work),
)
.map_err(parse_failure)?;
if matches!(mode, ReplayMode::ReferenceOutput)
&& !transform_chain_produces_binary(
classify_uri(uri) == UriClass::External,
&transforms,
)
{
enforce_transform_allowed(allowed, DEFAULT_IMPLICIT_C14N_URI).map_err(processing)?;
}
let timestamp = match mode {
ReplayMode::ReferenceOutput => None,
ReplayMode::Timestamp(method) => {
let method = self
.session
.view
.resolve_node(method)
.map_err(|error| processing(error.into()))?;
let uri = method.attribute("Algorithm").ok_or_else(|| {
processing(DsigError::InvalidStructure {
reason: "timestamp method has no Algorithm",
})
})?;
self.work
.charge_work(uri.len() as u64)
.map_err(|error| processing(error.into()))?;
enforce_current_transform_allowed(allowed, uri, self.work).map_err(processing)?;
let algorithm = super::super::parse::parse_replay_canonicalization_method(
method,
&crate::c14n::scratch::Accounted(self.work),
)
.map_err(parse_failure)?;
Some(algorithm)
}
};
let budget = &self.session.budgets.transforms;
let Some(data) = self.session.resolver.replay_input_from_with_budget(
uri,
node,
budget.node_set_materialization(),
budget.xml_base_resolution(),
&crate::c14n::scratch::Accounted(self.work),
)?
else {
return Ok(ReplayOutcome::Unavailable(ExternalResourceUnavailable {
reference: descriptor.identity,
}));
};
let enveloped_signature =
super::super::transforms::enveloped_signature_for_reference(node, &transforms, || {
self.work
.charge_work(1)
.map_err(|error| processing(error.into()))
})?;
super::super::transforms::replay_reference_transforms(
node,
enveloped_signature,
data,
&transforms,
super::super::transforms::ReplayExecution {
options: self.session.context.transform_options(),
budget,
work: self.work,
},
ReplayTerminal {
work: self.work,
receiver,
timestamp,
error: std::marker::PhantomData,
},
)?;
Ok(ReplayOutcome::Complete)
}
}