1use crate::xml_input as xml_sec_xml_input;
4use std::{collections::HashSet, io::Write};
5
6use base64::Engine;
7use xml_sec_xml_input::lexical::Writer;
8
9use crate::c14n::{C14nAlgorithm, C14nMode, canonicalize_bounded_with_xml_base_budget};
10use crate::policy::{PolicyViolation, SigningPolicy};
11use crate::xml::{is_xml_1_0_character, is_xml_ncname};
12
13use super::mutation::{
14 fill_signature_value_with_budget, fill_signed_info_digest_values_with_budget,
15 padded_base64_len_for_xml, projected_signature_value_output_len_at_index_with_budget,
16 zero_base64_placeholder,
17};
18use super::transforms::{
19 ENVELOPED_SIGNATURE_XPATH_EXPR, ENVELOPED_SIGNATURE_XPATH_PREFIX, TransformExecutionBudget,
20 XPathSignatureParseBudget, map_c14n_resource_policy_violation, transform_chain_produces_binary,
21 validate_signing_transform_policy, validate_xpath_namespace_budget_with_resources,
22};
23use super::uri::validate_signing_reference_uri;
24use super::{
25 BASE64_TRANSFORM_URI, DigestAlgorithm, ENVELOPED_SIGNATURE_URI, SignatureAlgorithm,
26 SignatureContext, Transform, XPATH_FILTER2_TRANSFORM_URI, XPATH_TRANSFORM_URI, XPathExpression,
27};
28
29const XMLDSIG_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
30const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
31const XMLNS_NS: &str = "http://www.w3.org/2000/xmlns/";
32const EXCLUSIVE_C14N_NS: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
33
34#[derive(Debug, thiserror::Error)]
36pub enum SignatureBuilderError {
37 #[error("invalid XML namespace prefix: {0}")]
39 InvalidNamespacePrefix(String),
40 #[error("XML namespace URI contains a character forbidden by XML 1.0: {0:?}")]
42 InvalidNamespaceUri(String),
43 #[error("XPath namespace binding conflicts with XMLDSig prefix: {0}")]
45 NamespacePrefixConflict(String),
46 #[error("invalid {element} Id: {value}")]
48 InvalidId {
49 element: &'static str,
51 value: String,
53 },
54 #[error("a signature template requires at least one Reference")]
56 MissingReference,
57 #[error("signing policy violation: {0}")]
59 Policy(#[from] PolicyViolation),
60 #[error("invalid XPath expression: {0}")]
62 InvalidXPath(String),
63
64 #[error("invalid transform: {0}")]
66 InvalidTransform(String),
67 #[error("algorithm is not allowed for signing: {0}")]
69 SigningAlgorithmDisabled(&'static str),
70 #[error("signature context is not applicable to algorithm: {0}")]
72 SignatureContextNotApplicable(&'static str),
73 #[error("RSA-PSS salt length exceeds xs:int")]
75 InvalidRsaPssParameters,
76 #[error("XML serialization error: {0}")]
78 Serialization(#[from] std::io::Error),
79 #[error("XML writer emitted invalid UTF-8: {0}")]
81 InvalidUtf8(#[from] std::string::FromUtf8Error),
82 #[error("generated XML template is invalid: {0}")]
84 GeneratedXml(#[from] crate::xml::dom::ParseError),
85 #[error("generated SignedInfo canonicalization failed: {0}")]
87 Canonicalization(#[from] crate::c14n::C14nError),
88 #[error("generated XML template is missing SignedInfo")]
90 MissingGeneratedSignedInfo,
91 #[error("generated XML template validation failed: {0}")]
93 GeneratedMutation(#[source] super::mutation::XmlMutationError),
94}
95
96#[derive(Debug, Clone)]
98pub struct ReferenceBuilder {
99 uri: String,
100 id: Option<String>,
101 ref_type: Option<String>,
102 transforms: Vec<Transform>,
103 digest_method: DigestAlgorithm,
104}
105
106impl ReferenceBuilder {
107 #[must_use]
109 pub fn new(digest_method: DigestAlgorithm) -> Self {
110 Self {
111 uri: String::new(),
112 id: None,
113 ref_type: None,
114 transforms: Vec::new(),
115 digest_method,
116 }
117 }
118
119 #[must_use]
124 pub fn uri(mut self, uri: impl Into<String>) -> Self {
125 self.uri = uri.into();
126 self
127 }
128
129 #[must_use]
131 pub fn id(mut self, id: impl Into<String>) -> Self {
132 self.id = Some(id.into());
133 self
134 }
135
136 #[must_use]
138 pub fn ref_type(mut self, ref_type: impl Into<String>) -> Self {
139 self.ref_type = Some(ref_type.into());
140 self
141 }
142
143 #[must_use]
145 pub fn transform(mut self, transform: Transform) -> Self {
146 self.transforms.push(transform);
147 self
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct SignatureBuilder {
154 c14n_method: C14nAlgorithm,
155 sign_method: SignatureAlgorithm,
156 signature_context: SignatureContext,
157 ns_prefix: Option<String>,
158 signature_id: Option<String>,
159 references: Vec<ReferenceBuilder>,
160 include_key_info: bool,
161}
162
163impl SignatureBuilder {
164 #[must_use]
166 pub fn new(c14n_method: C14nAlgorithm, sign_method: SignatureAlgorithm) -> Self {
167 Self {
168 c14n_method,
169 sign_method,
170 signature_context: SignatureContext::default(),
171 ns_prefix: None,
172 signature_id: None,
173 references: Vec::new(),
174 include_key_info: false,
175 }
176 }
177
178 #[must_use]
180 pub fn ns_prefix(mut self, prefix: impl Into<String>) -> Self {
181 self.ns_prefix = Some(prefix.into());
182 self
183 }
184
185 #[must_use]
187 pub fn signature_id(mut self, id: impl Into<String>) -> Self {
188 self.signature_id = Some(id.into());
189 self
190 }
191
192 #[must_use]
195 pub fn signature_context(mut self, context: SignatureContext) -> Self {
196 self.signature_context = context;
197 self
198 }
199
200 #[must_use]
202 pub fn add_reference(mut self, reference: ReferenceBuilder) -> Self {
203 self.references.push(reference);
204 self
205 }
206
207 #[must_use]
209 pub fn key_info(mut self, include: bool) -> Self {
210 self.include_key_info = include;
211 self
212 }
213
214 pub fn build_template(&self) -> Result<String, SignatureBuilderError> {
216 self.build_template_with_policy(&SigningPolicy::default())
217 }
218
219 pub fn build_template_with_policy(
224 &self,
225 policy: &SigningPolicy,
226 ) -> Result<String, SignatureBuilderError> {
227 let budget = TransformExecutionBudget::from_resources(&policy.resources);
228 let mut xpath_parse_budget = XPathSignatureParseBudget::from_resources(&policy.resources);
229 self.build_template_with_policy_and_signature_output_len(
230 policy,
231 None,
232 None,
233 &budget,
234 &mut xpath_parse_budget,
235 )
236 }
237
238 pub(super) fn build_template_with_policy_for_signature_output(
239 &self,
240 policy: &SigningPolicy,
241 signature_output_len: usize,
242 encapsulation: Option<crate::provider::KeyEncapsulationAlgorithm>,
243 budget: &TransformExecutionBudget,
244 xpath_parse_budget: &mut XPathSignatureParseBudget,
245 ) -> Result<String, SignatureBuilderError> {
246 self.build_template_with_policy_and_signature_output_len(
247 policy,
248 Some(signature_output_len),
249 encapsulation,
250 budget,
251 xpath_parse_budget,
252 )
253 }
254
255 fn build_template_with_policy_and_signature_output_len(
256 &self,
257 policy: &SigningPolicy,
258 signature_output_len: Option<usize>,
259 encapsulation: Option<crate::provider::KeyEncapsulationAlgorithm>,
260 budget: &TransformExecutionBudget,
261 xpath_parse_budget: &mut XPathSignatureParseBudget,
262 ) -> Result<String, SignatureBuilderError> {
263 policy.validate()?;
264 self.validate(policy, xpath_parse_budget)?;
265
266 let prefix = self.ns_prefix.as_deref();
267 let mut writer = Writer::new(Vec::new());
268 let signature_name = qualified_name(prefix, "Signature");
269 let namespace_attr = prefix.map_or_else(|| "xmlns".to_owned(), |p| format!("xmlns:{p}"));
270 let mut attributes = vec![(namespace_attr.as_str(), XMLDSIG_NS)];
271 if let Some(id) = &self.signature_id {
272 attributes.push(("Id", id.as_str()));
273 }
274 writer.start(&signature_name, attributes)?;
275
276 write_start(&mut writer, prefix, "SignedInfo")?;
277 write_algorithm(
278 &mut writer,
279 prefix,
280 "CanonicalizationMethod",
281 self.c14n_method.uri(),
282 )?;
283 if let SignatureAlgorithm::RsaPss(parameters) = self.sign_method {
284 let method_name = qualified_name(prefix, "SignatureMethod");
285 writer.start(&method_name, [("Algorithm", self.sign_method.uri())])?;
286 writer.start("RSAPSSParams", [("xmlns", super::parse::RSA_PSS_NS)])?;
289 writer.empty(
290 "DigestMethod",
291 [
292 ("xmlns", XMLDSIG_NS),
293 ("Algorithm", parameters.digest.uri()),
294 ],
295 )?;
296 writer.start(
297 "MaskGenerationFunction",
298 [("Algorithm", "http://www.w3.org/2007/05/xmldsig-more#MGF1")],
299 )?;
300 writer.empty(
301 "DigestMethod",
302 [
303 ("xmlns", XMLDSIG_NS),
304 ("Algorithm", parameters.mgf_digest.uri()),
305 ],
306 )?;
307 writer.end("MaskGenerationFunction")?;
308 writer.start("SaltLength", std::iter::empty::<(&str, &str)>())?;
309 writer.text(¶meters.salt_len.to_string())?;
310 writer.end("SaltLength")?;
311 writer.end("RSAPSSParams")?;
312 writer.end(&method_name)?;
313 } else if self.signature_context.as_bytes().is_empty() {
314 write_algorithm(
315 &mut writer,
316 prefix,
317 "SignatureMethod",
318 self.sign_method.uri(),
319 )?;
320 } else {
321 let method_name = qualified_name(prefix, "SignatureMethod");
322 writer.start(&method_name, [("Algorithm", self.sign_method.uri())])?;
323 let context_name = self.sign_method.context_element().ok_or(
328 SignatureBuilderError::SignatureContextNotApplicable(self.sign_method.uri()),
329 )?;
330 writer.start(
331 context_name,
332 [("xmlns", super::parse::EXPERIMENTAL_SIGNATURE_NS)],
333 )?;
334 let mut encoded = [0_u8; 340];
335 let length = base64::engine::general_purpose::STANDARD
336 .encode_slice(self.signature_context.as_bytes(), &mut encoded)
337 .map_err(std::io::Error::other)?;
338 writer.text(std::str::from_utf8(&encoded[..length]).map_err(std::io::Error::other)?)?;
339 writer.end(context_name)?;
340 writer.end(&method_name)?;
341 }
342 for reference in &self.references {
343 write_reference(&mut writer, prefix, reference)?;
344 }
345 write_end(&mut writer, prefix, "SignedInfo")?;
346 write_empty(&mut writer, prefix, "SignatureValue")?;
347 if let Some(algorithm) = encapsulation {
348 policy.key_establishment.check_encapsulation(algorithm)?;
349 write_start(&mut writer, prefix, "KeyInfo")?;
350 writer.start(
351 "EncapsulationMechanism",
352 [
353 ("xmlns", crate::key_establishment::ENCAPSULATION_NS),
354 ("Algorithm", algorithm.uri()),
355 ],
356 )?;
357 writer.empty("KeyInfo", [("xmlns", XMLDSIG_NS)])?;
358 writer.start(
359 "CipherData",
360 [("xmlns", "http://www.w3.org/2001/04/xmlenc#")],
361 )?;
362 writer.empty("CipherValue", std::iter::empty::<(&str, &str)>())?;
363 writer.end("CipherData")?;
364 writer.end("EncapsulationMechanism")?;
365 write_end(&mut writer, prefix, "KeyInfo")?;
366 } else if self.include_key_info {
367 write_empty(&mut writer, prefix, "KeyInfo")?;
368 }
369 writer.end(&signature_name)?;
370
371 let template = String::from_utf8(writer.into_inner())?;
372 policy.resources.validate_xml_document_len(template.len())?;
375 let digest_placeholders = self.references.iter().map(|reference| {
376 base64::engine::general_purpose::STANDARD.encode(vec![
377 0_u8;
378 reference
379 .digest_method
380 .output_len()
381 ])
382 });
383 let digest_validation_template = fill_signed_info_digest_values_with_budget(
384 &template,
385 digest_placeholders,
386 Some(policy),
387 Some(budget.xml_parse_work()),
388 )
389 .map_err(map_generated_mutation_error)?;
390 let validation_template = if let Some(signature_output_len) = signature_output_len {
391 let encoded_signature_len = padded_base64_len_for_xml(signature_output_len, policy)
392 .map_err(map_generated_mutation_error)?;
393 let projected_document_len = projected_signature_value_output_len_at_index_with_budget(
394 &digest_validation_template,
395 encoded_signature_len,
396 0,
397 Some(policy),
398 Some(budget.xml_parse_work()),
399 )
400 .map_err(map_generated_mutation_error)?;
401 policy
402 .resources
403 .validate_xml_document_len(projected_document_len)?;
404 let signature_placeholder =
405 zero_base64_placeholder(signature_output_len, encoded_signature_len);
406 fill_signature_value_with_budget(
407 &digest_validation_template,
408 &signature_placeholder,
409 Some(policy),
410 Some(budget.xml_parse_work()),
411 )
412 .map_err(map_generated_mutation_error)?
413 } else {
414 digest_validation_template
415 };
416 policy
417 .resources
418 .validate_xml_document_len(validation_template.len())?;
419 let settings =
420 crate::document::DocumentParseSettings::from_policy(&policy.xml, &policy.resources);
421 let document = super::mutation::parse_with_options_and_budget(
422 &validation_template,
423 settings,
424 Some(budget.xml_parse_work()),
425 )
426 .map_err(|error| match error.into_policy_violation(settings) {
427 Ok(error) => SignatureBuilderError::Policy(error),
428 Err(crate::document::XmlDocumentError::Parse(error)) => {
429 SignatureBuilderError::GeneratedXml(error)
430 }
431 Err(error) => SignatureBuilderError::GeneratedMutation(
432 super::mutation::XmlMutationError::Document(error),
433 ),
434 })?;
435 let signed_info = document
436 .root_element()
437 .children()
438 .find(|node| node.has_tag_name((XMLDSIG_NS, "SignedInfo")))
439 .ok_or(SignatureBuilderError::MissingGeneratedSignedInfo)?;
440 let signed_info_subtree: HashSet<_> =
441 signed_info.descendants().map(|node| node.id()).collect();
442 let mut canonical_signed_info = Vec::new();
443 canonicalize_bounded_with_xml_base_budget(
444 &document,
445 Some(&|node| signed_info_subtree.contains(&node.id())),
446 &self.c14n_method,
447 budget.remaining_c14n_output(),
448 budget.xml_base_resolution(),
449 &mut canonical_signed_info,
450 )
451 .map_err(|error| {
452 map_c14n_resource_policy_violation(
453 &error,
454 crate::policy::resource_name::CANONICALIZED_BYTES,
455 budget.c14n_output_limit(),
456 )
457 .map_or_else(
458 || SignatureBuilderError::Canonicalization(error),
459 SignatureBuilderError::Policy,
460 )
461 })?;
462 budget.charge_c14n_output_policy(canonical_signed_info.len())?;
463 Ok(template)
464 }
465
466 pub(super) fn signature_method(&self) -> SignatureAlgorithm {
467 self.sign_method
468 }
469
470 fn validate(
471 &self,
472 policy: &SigningPolicy,
473 xpath_signature_budget: &mut XPathSignatureParseBudget,
474 ) -> Result<(), SignatureBuilderError> {
475 if let Some(parameters) = self.sign_method.rsa_pss_parameters()
476 && parameters.salt_len > i32::MAX as usize
477 {
478 return Err(SignatureBuilderError::InvalidRsaPssParameters);
479 }
480 if !self.signature_context.as_bytes().is_empty()
481 && self.sign_method.context_element().is_none()
482 {
483 return Err(SignatureBuilderError::SignatureContextNotApplicable(
484 self.sign_method.uri(),
485 ));
486 }
487 if let Some(prefix) = &self.ns_prefix
488 && !is_namespace_prefix(prefix)
489 {
490 return Err(SignatureBuilderError::InvalidNamespacePrefix(
491 prefix.clone(),
492 ));
493 }
494 if let Some(id) = &self.signature_id
495 && !is_xml_ncname(id)
496 {
497 return Err(SignatureBuilderError::InvalidId {
498 element: "Signature",
499 value: id.clone(),
500 });
501 }
502 if self.references.is_empty() {
503 return Err(SignatureBuilderError::MissingReference);
504 }
505 if self.references.len() > policy.resources.max_references {
506 return Err(PolicyViolation::ResourceLimit {
507 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
508 maximum: policy.resources.max_references,
509 actual: self.references.len(),
510 }
511 .into());
512 }
513 for reference in &self.references {
514 validate_signing_reference_uri(&reference.uri, policy)?;
515 for transform in &reference.transforms {
518 if let Transform::Relationship(selectors) = transform {
519 xpath_signature_budget
520 .validate_relationship_selectors(selectors)
521 .map_err(|error| match error {
522 super::TransformError::Policy(violation) => {
523 SignatureBuilderError::Policy(violation)
524 }
525 other => SignatureBuilderError::InvalidTransform(other.to_string()),
526 })?;
527 }
528 }
529 let generated_transforms = reference_transforms_for_generation(reference);
530 super::transforms::validate_relationship_chain(
531 &generated_transforms,
532 policy.transforms.opc_relationship_edition,
533 )
534 .map_err(|error| SignatureBuilderError::InvalidTransform(error.to_string()))?;
535 if generated_transforms.len() > policy.resources.max_transforms_per_reference {
536 return Err(PolicyViolation::ResourceLimit {
537 resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
538 maximum: policy.resources.max_transforms_per_reference,
539 actual: generated_transforms.len(),
540 }
541 .into());
542 }
543 let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
544 validate_signing_transform_policy(
545 initial_binary,
546 &generated_transforms,
547 policy.transforms.allowed_algorithms.as_ref(),
548 )?;
549 for transform in &reference.transforms {
550 match transform {
551 Transform::XpathExcludeAllSignatures => {
552 validate_xpath_source(
553 ENVELOPED_SIGNATURE_XPATH_EXPR,
554 xpath_signature_budget,
555 )?;
556 }
557 Transform::XPath(xpath) => {
558 validate_xpath_source(xpath.expression(), xpath_signature_budget)?;
559 }
560 Transform::XPathFilter2(filters) => {
561 if filters.is_empty() {
562 return Err(SignatureBuilderError::InvalidXPath(
563 "XPath Filter 2.0 requires at least one expression".into(),
564 ));
565 }
566 if filters.len() > policy.resources.max_xpath_filters {
567 return Err(PolicyViolation::ResourceLimit {
568 resource: crate::policy::resource_name::XPATH_FILTERS,
569 maximum: policy.resources.max_xpath_filters,
570 actual: filters.len(),
571 }
572 .into());
573 }
574 for filter in filters {
575 validate_xpath_source(
576 filter.xpath().expression(),
577 xpath_signature_budget,
578 )?;
579 }
580 }
581 _ => {}
582 }
583 }
584 validate_xpath_namespace_budget_with_resources(
585 &reference.transforms,
586 self.ns_prefix.as_deref().map(|prefix| (prefix, XMLDSIG_NS)),
587 &policy.resources,
588 )
589 .map_err(map_transform_validation_error)?;
590 }
591 for (prefix, uri, shares_signature_namespace) in
592 self.references.iter().flat_map(|reference| {
593 reference
594 .transforms
595 .iter()
596 .flat_map(|transform| match transform {
597 Transform::XPath(xpath) => xpath
598 .namespaces()
599 .iter()
600 .map(|(prefix, uri)| (prefix, uri, true))
601 .collect::<Vec<_>>(),
602 Transform::XPathFilter2(filters) => filters
603 .iter()
604 .flat_map(|filter| {
605 filter
606 .xpath()
607 .namespaces()
608 .iter()
609 .map(|(prefix, uri)| (prefix, uri, false))
610 })
611 .collect(),
612 _ => Vec::new(),
613 })
614 })
615 {
616 if uri.is_empty() || uri == XMLNS_NS || !uri.chars().all(is_xml_1_0_character) {
619 return Err(SignatureBuilderError::InvalidNamespaceUri(uri.clone()));
620 }
621 if prefix == "xmlns"
622 || (prefix == "xml") != (uri == XML_NS)
623 || (prefix != "xml" && !is_namespace_prefix(prefix))
624 {
625 return Err(SignatureBuilderError::InvalidNamespacePrefix(
626 prefix.clone(),
627 ));
628 }
629 if shares_signature_namespace
632 && self.ns_prefix.as_ref() == Some(prefix)
633 && uri != XMLDSIG_NS
634 {
635 return Err(SignatureBuilderError::NamespacePrefixConflict(
636 prefix.clone(),
637 ));
638 }
639 }
640 policy.check_signature_algorithm(self.sign_method)?;
641 if policy
642 .transforms
643 .allowed_algorithms
644 .as_ref()
645 .is_some_and(|allowed| !allowed.contains(self.c14n_method.uri()))
646 {
647 return Err(PolicyViolation::Algorithm {
648 operation: "signing transform",
649 algorithm: self.c14n_method.uri().to_owned(),
650 }
651 .into());
652 }
653 for reference in &self.references {
654 if let Some(id) = &reference.id
655 && !is_xml_ncname(id)
656 {
657 return Err(SignatureBuilderError::InvalidId {
658 element: "Reference",
659 value: id.clone(),
660 });
661 }
662 policy.check_digest_algorithm(reference.digest_method)?;
663 }
664 Ok(())
665 }
666}
667
668fn validate_xpath_source(
669 source: &str,
670 budget: &mut XPathSignatureParseBudget,
671) -> Result<(), SignatureBuilderError> {
672 if let Some(character) = source
673 .chars()
674 .find(|character| !is_xml_1_0_character(*character))
675 {
676 return Err(SignatureBuilderError::InvalidXPath(format!(
677 "XPath expression contains a character forbidden by XML 1.0: {character:?}"
678 )));
679 }
680 budget
681 .validate_expression(source)
682 .map_err(map_transform_validation_error)
683}
684
685fn map_transform_validation_error(error: super::TransformError) -> SignatureBuilderError {
686 match error {
687 super::TransformError::Policy(error) => SignatureBuilderError::Policy(error),
688 error => SignatureBuilderError::InvalidXPath(error.to_string()),
689 }
690}
691
692fn map_generated_mutation_error(error: super::mutation::XmlMutationError) -> SignatureBuilderError {
693 match error {
694 super::mutation::XmlMutationError::Policy(violation) => {
695 SignatureBuilderError::Policy(violation)
696 }
697 other => SignatureBuilderError::GeneratedMutation(other),
698 }
699}
700
701fn write_reference<W: Write>(
702 writer: &mut Writer<W>,
703 prefix: Option<&str>,
704 reference: &ReferenceBuilder,
705) -> Result<(), std::io::Error> {
706 let name = qualified_name(prefix, "Reference");
707 let mut attributes = Vec::new();
708 if let Some(id) = &reference.id {
709 attributes.push(("Id", id.as_str()));
710 }
711 if let Some(ref_type) = &reference.ref_type {
712 attributes.push(("Type", ref_type.as_str()));
713 }
714 attributes.push(("URI", reference.uri.as_str()));
715 writer.start(&name, attributes)?;
716
717 let generated_transforms = reference_transforms_for_generation(reference);
718 if !generated_transforms.is_empty() {
719 write_start(writer, prefix, "Transforms")?;
720 for transform in &generated_transforms {
721 write_transform(writer, prefix, transform)?;
722 }
723 write_end(writer, prefix, "Transforms")?;
724 }
725 write_algorithm(
726 writer,
727 prefix,
728 "DigestMethod",
729 reference.digest_method.uri(),
730 )?;
731 write_empty(writer, prefix, "DigestValue")?;
732 writer.end(&name)?;
733 Ok(())
734}
735
736fn reference_transforms_for_generation(reference: &ReferenceBuilder) -> Vec<Transform> {
737 let mut transforms = reference.transforms.clone();
738 let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
739 if !transform_chain_produces_binary(initial_binary, &transforms) {
740 transforms.push(Transform::C14n(C14nAlgorithm::new(
741 C14nMode::Inclusive1_1,
742 false,
743 )));
744 }
745 transforms
746}
747
748fn write_transform<W: Write>(
749 writer: &mut Writer<W>,
750 prefix: Option<&str>,
751 transform: &Transform,
752) -> Result<(), std::io::Error> {
753 match transform {
754 Transform::Relationship(selectors) => {
755 let name = qualified_name(prefix, "Transform");
760 writer.start(&name, [("Algorithm", super::RELATIONSHIP_TRANSFORM_URI)])?;
761 for selector in selectors {
762 let (element, attribute, value) = selector.parameter();
763 writer.empty(
764 element,
765 [
766 (
767 "xmlns",
768 "http://schemas.openxmlformats.org/package/2006/digital-signature",
769 ),
770 (attribute, value),
771 ],
772 )?;
773 }
774 writer.end(&name)
775 }
776 Transform::Enveloped => {
777 write_algorithm(writer, prefix, "Transform", ENVELOPED_SIGNATURE_URI)
778 }
779 Transform::XpathExcludeAllSignatures => {
780 let name = qualified_name(prefix, "Transform");
781 writer.start(&name, [("Algorithm", XPATH_TRANSFORM_URI)])?;
782 let xpath_name = qualified_name(prefix, "XPath");
783 let namespace = format!("xmlns:{ENVELOPED_SIGNATURE_XPATH_PREFIX}");
784 writer.start(&xpath_name, [(namespace.as_str(), XMLDSIG_NS)])?;
785 writer.text(ENVELOPED_SIGNATURE_XPATH_EXPR)?;
786 writer.end(&xpath_name)?;
787 writer.end(&name)?;
788 Ok(())
789 }
790 Transform::XPath(xpath) => {
791 let transform_name = qualified_name(prefix, "Transform");
792 writer.start(&transform_name, [("Algorithm", XPATH_TRANSFORM_URI)])?;
793 write_xpath_expression(writer, prefix, "XPath", None, xpath)?;
794 writer.end(&transform_name)?;
795 Ok(())
796 }
797 Transform::XPathFilter2(filters) => {
798 let transform_name = qualified_name(prefix, "Transform");
799 writer.start(
800 &transform_name,
801 [("Algorithm", XPATH_FILTER2_TRANSFORM_URI)],
802 )?;
803 for filter in filters {
804 write_xpath_expression(
805 writer,
806 None,
807 "XPath",
808 Some(filter.operation().as_str()),
809 filter.xpath(),
810 )?;
811 }
812 writer.end(&transform_name)?;
813 Ok(())
814 }
815 Transform::Base64Decode => {
816 write_algorithm(writer, prefix, "Transform", BASE64_TRANSFORM_URI)
817 }
818 Transform::C14n(algorithm) if algorithm.inclusive_prefixes().is_empty() => {
819 write_algorithm(writer, prefix, "Transform", algorithm.uri())
820 }
821 Transform::C14n(algorithm) => {
822 let name = qualified_name(prefix, "Transform");
823 writer.start(&name, [("Algorithm", algorithm.uri())])?;
824
825 if algorithm.mode() == C14nMode::Exclusive1_0 {
826 let mut prefixes: Vec<&str> = algorithm
827 .inclusive_prefixes()
828 .iter()
829 .map(String::as_str)
830 .collect();
831 prefixes.sort_unstable();
832 let prefix_list = prefixes
833 .into_iter()
834 .map(|p| if p.is_empty() { "#default" } else { p })
835 .collect::<Vec<_>>()
836 .join(" ");
837 writer.empty(
838 "ec:InclusiveNamespaces",
839 [
840 ("xmlns:ec", EXCLUSIVE_C14N_NS),
841 ("PrefixList", prefix_list.as_str()),
842 ],
843 )?;
844 }
845 writer.end(&name)?;
846 Ok(())
847 }
848 }
849}
850
851fn write_xpath_expression<W: Write>(
852 writer: &mut Writer<W>,
853 prefix: Option<&str>,
854 local_name: &str,
855 filter: Option<&str>,
856 xpath: &XPathExpression,
857) -> Result<(), std::io::Error> {
858 let name = qualified_name(prefix, local_name);
859 let namespace_attributes = xpath
860 .namespaces()
861 .iter()
862 .filter(|(namespace_prefix, _)| namespace_prefix.as_str() != "xml")
863 .map(|(namespace_prefix, uri)| (format!("xmlns:{namespace_prefix}"), uri))
864 .collect::<Vec<_>>();
865 let mut attributes = Vec::new();
866 if prefix.is_none() && filter.is_some() {
867 attributes.push(("xmlns", XPATH_FILTER2_TRANSFORM_URI));
868 }
869 if let Some(filter) = filter {
870 attributes.push(("Filter", filter));
871 }
872 for (attribute, uri) in &namespace_attributes {
873 attributes.push((attribute.as_str(), uri.as_str()));
874 }
875 writer.start(&name, attributes)?;
876 writer.text(xpath.expression())?;
877 writer.end(&name)?;
878 Ok(())
879}
880
881fn write_algorithm<W: Write>(
882 writer: &mut Writer<W>,
883 prefix: Option<&str>,
884 local_name: &str,
885 algorithm: &str,
886) -> Result<(), std::io::Error> {
887 let name = qualified_name(prefix, local_name);
888 writer.empty(&name, [("Algorithm", algorithm)])?;
889 Ok(())
890}
891
892fn write_start<W: Write>(
893 writer: &mut Writer<W>,
894 prefix: Option<&str>,
895 local_name: &str,
896) -> Result<(), std::io::Error> {
897 writer.start(&qualified_name(prefix, local_name), [])?;
898 Ok(())
899}
900
901fn write_end<W: Write>(
902 writer: &mut Writer<W>,
903 prefix: Option<&str>,
904 local_name: &str,
905) -> Result<(), std::io::Error> {
906 writer.end(&qualified_name(prefix, local_name))?;
907 Ok(())
908}
909
910fn write_empty<W: Write>(
911 writer: &mut Writer<W>,
912 prefix: Option<&str>,
913 local_name: &str,
914) -> Result<(), std::io::Error> {
915 writer.empty(&qualified_name(prefix, local_name), [])?;
916 Ok(())
917}
918
919fn qualified_name(prefix: Option<&str>, local_name: &str) -> String {
920 prefix.map_or_else(
921 || local_name.to_owned(),
922 |prefix| format!("{prefix}:{local_name}"),
923 )
924}
925
926fn is_namespace_prefix(value: &str) -> bool {
927 !matches!(value, "xml" | "xmlns") && is_xml_ncname(value)
929}