1use base64::Engine;
9use p256::ecdsa::{Signature as P256Signature, SigningKey as P256SigningKey};
10use p256::pkcs8::{DecodePrivateKey, EncodePublicKey};
11use p384::ecdsa::{Signature as P384Signature, SigningKey as P384SigningKey};
12use roxmltree::{Document, Node, NodeId};
13use rsa::RsaPrivateKey;
14use rsa::pkcs1v15::Signature as RsaPkcs1v15Signature;
15use rsa::pkcs1v15::SigningKey as RsaPkcs1v15SigningKey;
16use rsa::signature::{RandomizedSigner, SignatureEncoding};
17use rsa::traits::PublicKeyParts;
18use sha2::{Sha256, Sha384, Sha512};
19use signature::hazmat::PrehashSigner;
20use std::{collections::HashSet, ops::Range};
21use x509_parser::prelude::FromDer;
22
23use crate::c14n::canonicalize_bounded_with_xml_base_budget;
24
25use super::builder::{SignatureBuilder, SignatureBuilderError};
26use super::digest::DigestAlgorithm;
27use super::mutation::{
28 XmlMutationError, fill_signed_info_digest_values_at_index_with_budget,
29 fill_signed_info_digest_values_with_budget, merge_key_info_source_at_index_with_budget,
30 padded_base64_len_for_xml,
31};
32use super::parse::{
33 MAX_REFERENCES_PER_SIGNATURE, SignatureAlgorithm, XMLDSIG_NS,
34 parse_signed_info_with_xpath_budget,
35};
36use super::signature::{encode_ecdsa_signature_as_der, maximum_ecdsa_der_signature_len};
37use super::transforms::{
38 Transform, TransformExecutionBudget, TransformOptions, XPathHereSemantics,
39 XPathSignatureParseBudget, execute_transforms_with_dependency_nodes,
40 execute_transforms_with_options_and_budget, map_c14n_resource_policy_violation,
41 parse_transforms_with_budget, validate_signing_transform_policy,
42};
43use super::types::TransformError;
44use super::uri::{UriReferenceResolver, validate_signing_reference_uri};
45use super::verify::parse_signature_children;
46use crate::document::{DocumentParseSettings, XmlDocument, XmlDocumentError, XmlParseWorkBudget};
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50#[must_use = "use the computed digest value to fill the corresponding <DigestValue>"]
51pub struct ComputedReferenceDigest {
52 pub index: usize,
54 pub uri: String,
56 pub digest_method: DigestAlgorithm,
58 pub digest_value: String,
60}
61
62#[derive(Debug, thiserror::Error)]
64pub enum SigningDigestError {
65 #[error("cryptographic provider error: {0}")]
67 Provider(#[from] crate::provider::ProviderError),
68
69 #[error("signing policy violation: {0}")]
74 Policy(#[from] crate::policy::PolicyViolation),
75
76 #[error("XML parse error: {0}")]
78 XmlParse(#[from] roxmltree::Error),
79
80 #[error("XML document error: {0}")]
82 Document(#[from] XmlDocumentError),
83
84 #[error("missing required element: <{element}>")]
86 MissingElement {
87 element: &'static str,
89 },
90
91 #[error("invalid signing template: {0}")]
93 InvalidStructure(String),
94
95 #[error("unsupported digest algorithm: {uri}")]
97 UnsupportedAlgorithm {
98 uri: String,
100 },
101
102 #[error("digest algorithm is disabled for signing: {uri}")]
104 SigningAlgorithmDisabled {
105 uri: &'static str,
107 },
108
109 #[error("reference processing error: {0}")]
111 Transform(#[from] TransformError),
112
113 #[error("XML mutation error: {0}")]
115 XmlMutation(#[from] XmlMutationError),
116}
117
118#[derive(Debug, thiserror::Error)]
120pub enum SigningError {
121 #[error("signing policy violation: {0}")]
123 Policy(#[from] crate::policy::PolicyViolation),
124
125 #[error("signing digest pass failed: {0}")]
127 Digest(SigningDigestError),
128
129 #[error("failed to parse SignedInfo after digest fill: {0}")]
131 ParseSignedInfo(super::parse::ParseError),
132
133 #[error("SignedInfo canonicalization failed: {0}")]
135 Canonicalization(#[from] crate::c14n::C14nError),
136
137 #[error("signing key error: {0}")]
139 Key(#[from] SigningKeyError),
140
141 #[error("signature output must be {expected} bytes, got {actual}")]
143 InvalidSignatureOutputLength {
144 expected: usize,
146 actual: usize,
148 },
149
150 #[error("XML mutation error: {0}")]
152 XmlMutation(XmlMutationError),
153
154 #[error("KeyInfo writer error: {0}")]
156 KeyInfo(#[from] KeyInfoWriteError),
157
158 #[error("XML document error: {0}")]
160 Document(#[from] XmlDocumentError),
161
162 #[error("signature template error: {0}")]
164 Template(SignatureBuilderError),
165}
166
167impl From<SigningDigestError> for SigningError {
168 fn from(error: SigningDigestError) -> Self {
169 match error {
170 SigningDigestError::XmlMutation(XmlMutationError::Policy(error)) => Self::Policy(error),
171 SigningDigestError::Policy(error)
172 | SigningDigestError::Transform(TransformError::Policy(error)) => Self::Policy(error),
173 SigningDigestError::Document(error) => Self::Document(error),
174 error => Self::Digest(error),
175 }
176 }
177}
178
179impl From<super::parse::ParseError> for SigningError {
180 fn from(error: super::parse::ParseError) -> Self {
181 match error {
182 super::parse::ParseError::Policy(error)
183 | super::parse::ParseError::Transform(TransformError::Policy(error)) => {
184 Self::Policy(error)
185 }
186 error => Self::ParseSignedInfo(error),
187 }
188 }
189}
190
191impl From<XmlMutationError> for SigningError {
192 fn from(error: XmlMutationError) -> Self {
193 match error {
194 XmlMutationError::Policy(error) => Self::Policy(error),
195 error => Self::XmlMutation(error),
196 }
197 }
198}
199
200impl From<SignatureBuilderError> for SigningError {
201 fn from(error: SignatureBuilderError) -> Self {
202 match error {
203 SignatureBuilderError::Policy(error) => Self::Policy(error),
204 error => Self::Template(error),
205 }
206 }
207}
208
209#[derive(Debug, thiserror::Error)]
211#[non_exhaustive]
212pub enum SigningKeyError {
213 #[error("cryptographic provider error: {0}")]
215 Provider(#[from] crate::provider::ProviderError),
216
217 #[error("invalid PEM private key")]
219 InvalidKeyPem,
220
221 #[error("invalid key format: expected PRIVATE KEY PEM, got {label}")]
223 InvalidKeyFormat {
224 label: String,
226 },
227
228 #[error("invalid PKCS#8 private key DER")]
230 InvalidKeyDer,
231
232 #[error("signing key does not support algorithm: {uri}")]
234 UnsupportedAlgorithm {
235 uri: String,
237 },
238
239 #[error("private-key signing operation failed")]
241 SigningFailed,
242
243 #[error("failed to encode signing public key as SPKI DER")]
245 PublicKeyEncodingFailed,
246
247 #[error("invalid signing public-key metadata")]
249 InvalidPublicKeyInfo,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
254#[non_exhaustive]
255pub enum SigningPublicKeyInfo {
256 Rsa {
258 spki_der: Vec<u8>,
260 modulus: Vec<u8>,
262 exponent: Vec<u8>,
264 },
265 Ec {
267 spki_der: Vec<u8>,
269 curve_oid: &'static str,
271 public_key: Vec<u8>,
273 },
274}
275
276impl SigningPublicKeyInfo {
277 #[must_use]
279 pub fn spki_der(&self) -> &[u8] {
280 match self {
281 Self::Rsa { spki_der, .. } | Self::Ec { spki_der, .. } => spki_der,
282 }
283 }
284}
285
286pub fn validate_signing_key(
291 key: &dyn SigningKey,
292 algorithm: SignatureAlgorithm,
293 policy: &crate::policy::SigningPolicy,
294) -> Result<(), SigningError> {
295 policy.resources.validate_key_candidates(1)?;
296 if !algorithm.signing_allowed() {
297 return Err(SigningKeyError::UnsupportedAlgorithm {
298 uri: algorithm.uri().to_owned(),
299 }
300 .into());
301 }
302 expected_signature_output_len(key, algorithm, policy).map(|_| ())
303}
304
305fn expected_signature_output_len(
306 key: &dyn SigningKey,
307 algorithm: SignatureAlgorithm,
308 policy: &crate::policy::SigningPolicy,
309) -> Result<usize, SigningError> {
310 let public_key = key.public_key_info()?;
311 let expected = match (algorithm, public_key) {
312 (
313 SignatureAlgorithm::RsaSha1
314 | SignatureAlgorithm::RsaSha256
315 | SignatureAlgorithm::RsaSha384
316 | SignatureAlgorithm::RsaSha512,
317 SigningPublicKeyInfo::Rsa {
318 modulus, exponent, ..
319 },
320 ) => policy
321 .rsa_keys
322 .validate_components("signing", &modulus, &exponent)?,
323 (
324 SignatureAlgorithm::EcdsaSha256 | SignatureAlgorithm::EcdsaSha384,
325 SigningPublicKeyInfo::Ec { public_key, .. },
326 ) if public_key.first() == Some(&0x04)
327 && public_key.len() > 1
328 && (public_key.len() - 1).is_multiple_of(2) =>
329 {
330 public_key.len() - 1
333 }
334 (
335 SignatureAlgorithm::RsaSha1
336 | SignatureAlgorithm::RsaSha256
337 | SignatureAlgorithm::RsaSha384
338 | SignatureAlgorithm::RsaSha512,
339 SigningPublicKeyInfo::Ec { .. },
340 )
341 | (
342 SignatureAlgorithm::EcdsaSha256 | SignatureAlgorithm::EcdsaSha384,
343 SigningPublicKeyInfo::Rsa { .. },
344 ) => {
345 return Err(SigningKeyError::UnsupportedAlgorithm {
346 uri: algorithm.uri().to_owned(),
347 }
348 .into());
349 }
350 _ => return Err(SigningKeyError::InvalidPublicKeyInfo.into()),
351 };
352 Ok(expected)
353}
354
355fn validate_signature_output(expected: usize, signature: &[u8]) -> Result<(), SigningError> {
356 if signature.len() != expected {
357 return Err(SigningError::InvalidSignatureOutputLength {
358 expected,
359 actual: signature.len(),
360 });
361 }
362 Ok(())
363}
364
365pub trait SigningKey {
367 fn sign(
369 &self,
370 algorithm: SignatureAlgorithm,
371 canonical_signed_info: &[u8],
372 ) -> Result<Vec<u8>, SigningKeyError>;
373
374 fn sign_with_provider(
379 &self,
380 provider: &dyn crate::provider::CryptoProvider,
381 algorithm: SignatureAlgorithm,
382 canonical_signed_info: &[u8],
383 ) -> Result<Vec<u8>, SigningKeyError> {
384 let _ = provider;
385 self.sign(algorithm, canonical_signed_info)
386 }
387
388 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError>;
390}
391
392pub trait KeyInfoWriter {
394 fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError>;
396}
397
398#[derive(Debug, thiserror::Error)]
400#[non_exhaustive]
401pub enum KeyInfoWriteError {
402 #[error("invalid PEM certificate")]
404 InvalidCertificatePem,
405
406 #[error("invalid certificate format: expected CERTIFICATE PEM, got {label}")]
408 InvalidCertificateFormat {
409 label: String,
411 },
412
413 #[error("invalid X.509 certificate DER")]
415 InvalidCertificateDer,
416
417 #[error("X.509 certificate chain must not be empty")]
419 EmptyCertificateChain,
420
421 #[error("signing key public-key extraction failed: {0}")]
423 SigningKey(#[from] SigningKeyError),
424
425 #[error("X.509 certificate public key does not match signing key")]
427 CertificateKeyMismatch,
428}
429
430pub struct X509CertificateKeyInfoWriter {
432 certificates_der: Vec<Vec<u8>>,
433}
434
435impl X509CertificateKeyInfoWriter {
436 pub fn from_pem(certificate_pem: &str) -> Result<Self, KeyInfoWriteError> {
438 Self::from_pem_chain([certificate_pem])
439 }
440
441 pub fn from_pem_chain<I, S>(certificate_pems: I) -> Result<Self, KeyInfoWriteError>
447 where
448 I: IntoIterator<Item = S>,
449 S: AsRef<str>,
450 {
451 let mut certificates_der = Vec::new();
452 for certificate_pem in certificate_pems {
453 certificates_der.push(parse_certificate_pem(certificate_pem.as_ref())?);
454 }
455 Self::from_der_chain(certificates_der)
456 }
457
458 pub fn from_der(certificate_der: &[u8]) -> Result<Self, KeyInfoWriteError> {
460 Self::from_der_chain([certificate_der])
461 }
462
463 pub fn from_der_chain<I, B>(certificates_der: I) -> Result<Self, KeyInfoWriteError>
469 where
470 I: IntoIterator<Item = B>,
471 B: AsRef<[u8]>,
472 {
473 let certificates_der = certificates_der
474 .into_iter()
475 .map(|certificate_der| {
476 let certificate_der = certificate_der.as_ref();
477 let (rest, _) =
478 x509_parser::certificate::X509Certificate::from_der(certificate_der)
479 .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
480 if !rest.is_empty() {
481 return Err(KeyInfoWriteError::InvalidCertificateDer);
482 }
483 Ok(certificate_der.to_vec())
484 })
485 .collect::<Result<Vec<_>, _>>()?;
486 if certificates_der.is_empty() {
487 return Err(KeyInfoWriteError::EmptyCertificateChain);
488 }
489 Ok(Self { certificates_der })
490 }
491}
492
493fn parse_certificate_pem(certificate_pem: &str) -> Result<Vec<u8>, KeyInfoWriteError> {
494 let (rest, pem) = x509_parser::pem::parse_x509_pem(certificate_pem.as_bytes())
495 .map_err(|_| KeyInfoWriteError::InvalidCertificatePem)?;
496 if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
497 return Err(KeyInfoWriteError::InvalidCertificatePem);
498 }
499 if pem.label != "CERTIFICATE" {
500 return Err(KeyInfoWriteError::InvalidCertificateFormat { label: pem.label });
501 }
502 Ok(pem.contents)
503}
504
505impl KeyInfoWriter for X509CertificateKeyInfoWriter {
506 fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
507 let leaf_der = &self.certificates_der[0];
508 let (rest, certificate) = x509_parser::certificate::X509Certificate::from_der(leaf_der)
509 .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
510 if !rest.is_empty() {
511 return Err(KeyInfoWriteError::InvalidCertificateDer);
512 }
513 let signing_public_key = signing_key.public_key_info()?;
514 if certificate.public_key().raw != signing_public_key.spki_der() {
515 return Err(KeyInfoWriteError::CertificateKeyMismatch);
516 }
517
518 let mut xml = format!("<X509Data xmlns=\"{XMLDSIG_NS}\">");
519 for certificate_der in &self.certificates_der {
520 let certificate_b64 = base64::engine::general_purpose::STANDARD.encode(certificate_der);
521 xml.push_str("<X509Certificate>");
522 xml.push_str(&certificate_b64);
523 xml.push_str("</X509Certificate>");
524 }
525 xml.push_str("</X509Data>");
526 Ok(xml)
527 }
528}
529
530pub struct RsaSigningKey {
532 key: RsaPrivateKey,
533}
534
535impl RsaSigningKey {
536 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
538 let private_key_der = parse_private_key_pem(private_key_pem)?;
539 Self::from_pkcs8_der(&private_key_der)
540 }
541
542 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
544 let key = RsaPrivateKey::from_pkcs8_der(private_key_der)
545 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
546 Ok(Self { key })
547 }
548}
549
550impl SigningKey for RsaSigningKey {
551 fn sign(
552 &self,
553 algorithm: SignatureAlgorithm,
554 canonical_signed_info: &[u8],
555 ) -> Result<Vec<u8>, SigningKeyError> {
556 self.sign_with_provider(
557 crate::provider::default_provider(),
558 algorithm,
559 canonical_signed_info,
560 )
561 }
562
563 fn sign_with_provider(
564 &self,
565 provider: &dyn crate::provider::CryptoProvider,
566 algorithm: SignatureAlgorithm,
567 canonical_signed_info: &[u8],
568 ) -> Result<Vec<u8>, SigningKeyError> {
569 match algorithm {
570 SignatureAlgorithm::RsaSha256 => sign_rsa_pkcs1v15_with_rng(
571 provider,
572 RsaPkcs1v15SigningKey::<Sha256>::new(self.key.clone()),
573 canonical_signed_info,
574 ),
575 SignatureAlgorithm::RsaSha384 => sign_rsa_pkcs1v15_with_rng(
576 provider,
577 RsaPkcs1v15SigningKey::<Sha384>::new(self.key.clone()),
578 canonical_signed_info,
579 ),
580 SignatureAlgorithm::RsaSha512 => sign_rsa_pkcs1v15_with_rng(
581 provider,
582 RsaPkcs1v15SigningKey::<Sha512>::new(self.key.clone()),
583 canonical_signed_info,
584 ),
585 _ => Err(SigningKeyError::UnsupportedAlgorithm {
586 uri: algorithm.uri().to_string(),
587 }),
588 }
589 }
590
591 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
592 let public_key = self.key.to_public_key();
593 let spki_der = public_key
594 .to_public_key_der()
595 .map(|doc| doc.as_bytes().to_vec())
596 .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
597 Ok(SigningPublicKeyInfo::Rsa {
598 spki_der,
599 modulus: public_key.n().to_be_bytes_trimmed_vartime().into_vec(),
600 exponent: public_key.e().to_be_bytes_trimmed_vartime().into_vec(),
601 })
602 }
603}
604
605fn sign_rsa_pkcs1v15_with_rng(
606 provider: &dyn crate::provider::CryptoProvider,
607 key: impl RandomizedSigner<RsaPkcs1v15Signature>,
608 canonical_signed_info: &[u8],
609) -> Result<Vec<u8>, SigningKeyError> {
610 let mut rng = crate::provider::ProviderRng(provider);
611 let signature = key
612 .try_sign_with_rng(&mut rng, canonical_signed_info)
613 .map_err(|_| SigningKeyError::SigningFailed)?;
614 Ok(signature.to_vec())
615}
616
617pub struct EcdsaP256SigningKey {
619 key: P256SigningKey,
620}
621
622impl EcdsaP256SigningKey {
623 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
625 let private_key_der = parse_private_key_pem(private_key_pem)?;
626 Self::from_pkcs8_der(&private_key_der)
627 }
628
629 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
631 let key = P256SigningKey::from_pkcs8_der(private_key_der)
632 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
633 Ok(Self { key })
634 }
635}
636
637impl SigningKey for EcdsaP256SigningKey {
638 fn sign(
639 &self,
640 algorithm: SignatureAlgorithm,
641 canonical_signed_info: &[u8],
642 ) -> Result<Vec<u8>, SigningKeyError> {
643 self.sign_with_provider(
644 crate::provider::default_provider(),
645 algorithm,
646 canonical_signed_info,
647 )
648 }
649
650 fn sign_with_provider(
651 &self,
652 provider: &dyn crate::provider::CryptoProvider,
653 algorithm: SignatureAlgorithm,
654 canonical_signed_info: &[u8],
655 ) -> Result<Vec<u8>, SigningKeyError> {
656 let digest_algorithm = match algorithm {
657 SignatureAlgorithm::EcdsaSha256 => DigestAlgorithm::Sha256,
658 SignatureAlgorithm::EcdsaSha384 => DigestAlgorithm::Sha384,
659 _ => {
660 return Err(SigningKeyError::UnsupportedAlgorithm {
661 uri: algorithm.uri().to_string(),
662 });
663 }
664 };
665 let prehash =
666 super::compute_digest_with_provider(provider, digest_algorithm, canonical_signed_info)?;
667 let signature: P256Signature = self
668 .key
669 .sign_prehash(&prehash)
670 .map_err(|_| SigningKeyError::SigningFailed)?;
671 Ok(signature.to_bytes().to_vec())
672 }
673
674 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
675 let verifying_key = self.key.verifying_key();
676 let spki_der = verifying_key
677 .to_public_key_der()
678 .map(|doc| doc.as_bytes().to_vec())
679 .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
680 Ok(SigningPublicKeyInfo::Ec {
681 spki_der,
682 curve_oid: "1.2.840.10045.3.1.7",
683 public_key: verifying_key.to_sec1_point(false).as_bytes().to_vec(),
684 })
685 }
686}
687
688pub struct EcdsaP384SigningKey {
690 key: P384SigningKey,
691}
692
693impl EcdsaP384SigningKey {
694 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
696 let private_key_der = parse_private_key_pem(private_key_pem)?;
697 Self::from_pkcs8_der(&private_key_der)
698 }
699
700 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
702 let key = P384SigningKey::from_pkcs8_der(private_key_der)
703 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
704 Ok(Self { key })
705 }
706}
707
708impl SigningKey for EcdsaP384SigningKey {
709 fn sign(
710 &self,
711 algorithm: SignatureAlgorithm,
712 canonical_signed_info: &[u8],
713 ) -> Result<Vec<u8>, SigningKeyError> {
714 self.sign_with_provider(
715 crate::provider::default_provider(),
716 algorithm,
717 canonical_signed_info,
718 )
719 }
720
721 fn sign_with_provider(
722 &self,
723 provider: &dyn crate::provider::CryptoProvider,
724 algorithm: SignatureAlgorithm,
725 canonical_signed_info: &[u8],
726 ) -> Result<Vec<u8>, SigningKeyError> {
727 let digest_algorithm = match algorithm {
728 SignatureAlgorithm::EcdsaSha256 => DigestAlgorithm::Sha256,
729 SignatureAlgorithm::EcdsaSha384 => DigestAlgorithm::Sha384,
730 _ => {
731 return Err(SigningKeyError::UnsupportedAlgorithm {
732 uri: algorithm.uri().to_string(),
733 });
734 }
735 };
736 let prehash =
737 super::compute_digest_with_provider(provider, digest_algorithm, canonical_signed_info)?;
738 let signature: P384Signature = self
739 .key
740 .sign_prehash(&prehash)
741 .map_err(|_| SigningKeyError::SigningFailed)?;
742 Ok(signature.to_bytes().to_vec())
743 }
744
745 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
746 let verifying_key = self.key.verifying_key();
747 let spki_der = verifying_key
748 .to_public_key_der()
749 .map(|doc| doc.as_bytes().to_vec())
750 .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
751 Ok(SigningPublicKeyInfo::Ec {
752 spki_der,
753 curve_oid: "1.3.132.0.34",
754 public_key: verifying_key.to_sec1_point(false).as_bytes().to_vec(),
755 })
756 }
757}
758
759#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
761pub enum SignatureTemplateSelection {
762 FirstDescendant,
764 #[default]
766 LastDescendant,
767}
768
769impl SignatureTemplateSelection {
770 const fn target(self) -> SigningSignatureTarget {
771 match self {
772 Self::FirstDescendant => SigningSignatureTarget::First,
773 Self::LastDescendant => SigningSignatureTarget::Last,
774 }
775 }
776}
777
778pub struct SignContext<'a> {
780 signing_key: &'a dyn SigningKey,
781 key_info_writer: Option<&'a dyn KeyInfoWriter>,
782 start_node_id: Option<&'a str>,
783 id_attributes: &'a [crate::IdAttributeRegistration],
784 template_selection: SignatureTemplateSelection,
785 policy: crate::policy::SigningPolicy,
786 provider: &'a dyn crate::provider::CryptoProvider,
787}
788
789impl<'a> SignContext<'a> {
790 pub fn new(signing_key: &'a dyn SigningKey) -> Self {
792 Self {
793 signing_key,
794 key_info_writer: None,
795 start_node_id: None,
796 id_attributes: &[],
797 template_selection: SignatureTemplateSelection::default(),
798 policy: crate::policy::SigningPolicy::default(),
799 provider: crate::provider::default_provider(),
800 }
801 }
802
803 #[must_use]
805 pub fn policy(mut self, policy: crate::policy::SigningPolicy) -> Self {
806 self.policy = policy;
807 self
808 }
809
810 #[must_use]
812 pub fn provider(mut self, provider: &'a dyn crate::provider::CryptoProvider) -> Self {
813 self.provider = provider;
814 self
815 }
816
817 #[must_use]
819 pub fn key_info_writer(mut self, writer: &'a dyn KeyInfoWriter) -> Self {
820 self.key_info_writer = Some(writer);
821 self
822 }
823
824 #[must_use]
829 pub fn start_node_id(mut self, id: &'a str) -> Self {
830 self.start_node_id = Some(id);
831 self
832 }
833
834 #[must_use]
840 pub fn signature_template_selection(mut self, selection: SignatureTemplateSelection) -> Self {
841 self.template_selection = selection;
842 self
843 }
844
845 #[must_use]
847 pub fn id_attributes(mut self, registrations: &'a [crate::IdAttributeRegistration]) -> Self {
848 self.id_attributes = registrations;
849 self
850 }
851
852 #[must_use]
858 pub fn xpath_here_semantics(mut self, semantics: XPathHereSemantics) -> Self {
859 self.policy.transforms.xpath_here_semantics = semantics;
860 self
861 }
862
863 pub fn sign_template(&self, xml: &str) -> Result<String, SigningError> {
872 self.policy.validate()?;
873 self.policy.resources.validate_xml_document_len(xml.len())?;
874 let mut budgets = SigningOperationBudgets::from_resources(&self.policy.resources);
875 let mut document = XmlDocument::parse_with_settings_and_budget(
876 xml.to_owned(),
877 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources),
878 budgets.transforms.xml_parse_work(),
879 )
880 .map_err(|error| match owned_document_policy_violation(error) {
881 Ok(error) => SigningError::Policy(error),
882 Err(XmlDocumentError::Parse(error)) => {
883 SigningError::Digest(SigningDigestError::XmlParse(error))
884 }
885 Err(error) => SigningError::Document(error),
886 })?;
887 self.validate_owned_document_input(&document)?;
888 self.sign_document_in_place(&mut document, &mut budgets)?;
889 Ok(document.into_xml())
890 }
891
892 pub fn sign_document(&self, document: &mut XmlDocument) -> Result<(), SigningError> {
897 self.validate_owned_document_input(document)?;
898 let mut budgets = SigningOperationBudgets::from_resources(&self.policy.resources);
899 let mut staged = document
900 .staged_copy_with_budget(
901 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources),
902 budgets.transforms.xml_parse_work(),
903 )
904 .map_err(map_owned_document_mutation_error)?;
905 self.sign_document_in_place(&mut staged, &mut budgets)?;
906 document
907 .commit_staged(staged)
908 .map_err(map_owned_document_mutation_error)
909 }
910
911 fn sign_document_in_place(
912 &self,
913 document: &mut XmlDocument,
914 budgets: &mut SigningOperationBudgets,
915 ) -> Result<(), SigningError> {
916 let target_signature = document.with_view(|view| {
917 signing_signature_index(
918 view.document(),
919 self.start_node_id,
920 self.id_attributes,
921 self.template_selection,
922 )
923 })?;
924 self.policy.resources.validate_key_candidates(1)?;
925 self.sign_template_at_index_with_budgets(document, target_signature, budgets)?;
926 Ok(())
927 }
928
929 fn validate_owned_document_input(&self, document: &XmlDocument) -> Result<(), SigningError> {
930 self.policy.validate()?;
931 document.validate_operation_policy(&self.policy.xml, &self.policy.resources)?;
932 Ok(())
933 }
934
935 fn sign_template_at_index_with_budgets(
936 &self,
937 document: &mut XmlDocument,
938 target_signature: usize,
939 budgets: &mut SigningOperationBudgets,
940 ) -> Result<(), SigningError> {
941 document.with_view(|view| {
942 let signature = find_signing_signature_node(
943 view.document(),
944 SigningSignatureTarget::Index(target_signature),
945 )?;
946 parse_signature_children(signature)
947 .map_err(|error| SigningDigestError::InvalidStructure(error.to_string()))?;
948 Ok::<_, SigningError>(())
949 })?;
950 let transform_options = TransformOptions::default()
951 .allow_internal_dtd(self.policy.xml.allow_internal_dtd)
952 .xpath_here_semantics(self.policy.transforms.xpath_here_semantics);
953 let with_key_info = if let Some(writer) = self.key_info_writer {
954 let key_info_content = writer.write_key_info(self.signing_key)?;
955 self.policy
958 .resources
959 .validate_xml_document_len(key_info_content.len())?;
960 let populated = merge_key_info_source_at_index_with_budget(
963 document.as_xml(),
964 &key_info_content,
965 target_signature,
966 Some(&self.policy),
967 Some(budgets.transforms.xml_parse_work()),
968 )?;
969 self.policy
970 .resources
971 .validate_xml_document_len(populated.len())?;
972 Some(populated)
973 } else {
974 None
975 };
976 if let Some(populated) = with_key_info {
977 document
978 .replace_serialized_with_settings(
979 populated,
980 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources),
981 Some(budgets.transforms.xml_parse_work()),
982 )
983 .map_err(map_owned_document_mutation_error)?;
984 }
985 fill_reference_digest_values_in_dependency_order(
986 document,
987 transform_options,
988 &self.policy,
989 self.provider,
990 budgets,
991 target_signature,
992 self.id_attributes,
993 )?;
994 self.policy
995 .resources
996 .validate_xml_document_len(document.as_xml().len())?;
997 let (algorithm, canonical_signed_info) =
998 canonicalize_signed_info(document, &self.policy, budgets, target_signature)?;
999 budgets
1000 .transforms
1001 .charge_c14n_output(canonical_signed_info.len())
1002 .map_err(SigningDigestError::Transform)?;
1003 if !algorithm.signing_allowed()
1004 || self
1005 .policy
1006 .signature_algorithms
1007 .as_ref()
1008 .is_some_and(|allowed| !allowed.contains(&algorithm))
1009 {
1010 return Err(crate::policy::PolicyViolation::Algorithm {
1011 operation: "signing",
1012 algorithm: algorithm.uri().to_string(),
1013 }
1014 .into());
1015 }
1016 let expected_signature_len =
1017 expected_signature_output_len(self.signing_key, algorithm, &self.policy)?;
1018 let projected_signature_len = projected_signature_output_len(
1019 algorithm,
1020 expected_signature_len,
1021 self.policy.ecdsa_signature_value_encoding,
1022 )?;
1023 let encoded_signature_len =
1024 padded_base64_len_for_xml(projected_signature_len, &self.policy)?;
1025 let signature_value_node = document.with_view(|view| {
1026 let signature = find_signing_signature_node(
1027 view.document(),
1028 SigningSignatureTarget::Index(target_signature),
1029 )?;
1030 let signature_value = find_required_child(signature, "SignatureValue")?;
1031 Ok::<_, SigningError>(view.node_identity(signature_value))
1032 })?;
1033 let projected_document_len = document
1034 .projected_content_replacement_len(signature_value_node, encoded_signature_len)?;
1035 self.policy
1036 .resources
1037 .validate_xml_document_len(projected_document_len)?;
1038 self.provider
1039 .require_capability(crate::provider::ProviderCapability::Sign(algorithm))
1040 .map_err(SigningKeyError::from)?;
1041 let signature_value =
1042 self.provider
1043 .sign(self.signing_key, algorithm, &canonical_signed_info)?;
1044 validate_signature_output(expected_signature_len, &signature_value)?;
1045 let signature_value = encode_signature_output(
1046 algorithm,
1047 signature_value,
1048 self.policy.ecdsa_signature_value_encoding,
1049 )?;
1050 let signature_b64 = base64::engine::general_purpose::STANDARD.encode(signature_value);
1051 document
1052 .replace_base64_contents_with_budget(
1053 &[(signature_value_node, signature_b64)],
1054 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources),
1055 budgets.transforms.xml_parse_work(),
1056 )
1057 .map_err(map_owned_document_mutation_error)?;
1058 self.policy
1059 .resources
1060 .validate_xml_document_len(document.as_xml().len())?;
1061 Ok(())
1062 }
1063
1064 pub fn sign_with_builder(
1067 &self,
1068 xml: &str,
1069 builder: &SignatureBuilder,
1070 ) -> Result<String, SigningError> {
1071 self.policy.validate()?;
1072 self.policy.resources.validate_xml_document_len(xml.len())?;
1073 let mut budgets = SigningOperationBudgets::from_resources(&self.policy.resources);
1074 let mut document = XmlDocument::parse_with_settings_and_budget(
1075 xml.to_owned(),
1076 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources),
1077 budgets.transforms.xml_parse_work(),
1078 )
1079 .map_err(|error| match owned_document_policy_violation(error) {
1080 Ok(error) => SigningError::Policy(error),
1081 Err(XmlDocumentError::Parse(error)) => {
1082 SigningError::XmlMutation(XmlMutationError::XmlParse(error))
1083 }
1084 Err(error) => SigningError::Document(error),
1085 })?;
1086 self.validate_owned_document_input(&document)?;
1087 self.sign_document_with_builder_in_place(&mut document, builder, &mut budgets)?;
1088 Ok(document.into_xml())
1089 }
1090
1091 pub fn sign_document_with_builder(
1093 &self,
1094 document: &mut XmlDocument,
1095 builder: &SignatureBuilder,
1096 ) -> Result<(), SigningError> {
1097 self.validate_owned_document_input(document)?;
1098 let mut budgets = SigningOperationBudgets::from_resources(&self.policy.resources);
1099 let mut staged = document
1100 .staged_copy_with_budget(
1101 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources),
1102 budgets.transforms.xml_parse_work(),
1103 )
1104 .map_err(map_owned_document_mutation_error)?;
1105 self.sign_document_with_builder_in_place(&mut staged, builder, &mut budgets)?;
1106 document
1107 .commit_staged(staged)
1108 .map_err(map_owned_document_mutation_error)
1109 }
1110
1111 fn sign_document_with_builder_in_place(
1112 &self,
1113 document: &mut XmlDocument,
1114 builder: &SignatureBuilder,
1115 budgets: &mut SigningOperationBudgets,
1116 ) -> Result<(), SigningError> {
1117 self.policy.resources.validate_key_candidates(1)?;
1118 let expected_signature_len = expected_signature_output_len(
1119 self.signing_key,
1120 builder.signature_method(),
1121 &self.policy,
1122 )?;
1123 let template = builder.build_template_with_policy_for_signature_output(
1124 &self.policy,
1125 expected_signature_len,
1126 &budgets.transforms,
1127 &mut budgets.xpath_parse,
1128 )?;
1129 let signature_parent = if let Some(id) = self.start_node_id {
1130 document.with_view(|view| {
1131 let start = signing_start_node(view.document(), id, self.id_attributes)?;
1132 Ok::<_, SigningError>(view.node_identity(start))
1133 })?
1134 } else {
1135 document.with_view(|view| view.root_element())
1136 };
1137 let projected_document_len =
1138 document.projected_child_append_len(signature_parent, template.len())?;
1139 self.policy
1140 .resources
1141 .validate_xml_document_len(projected_document_len)?;
1142 document
1143 .append_generated_child_with_budget(
1144 signature_parent,
1145 &template,
1146 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources),
1147 budgets.transforms.xml_parse_work(),
1148 )
1149 .map_err(map_owned_document_mutation_error)?;
1150 self.policy
1151 .resources
1152 .validate_xml_document_len(document.as_xml().len())?;
1153 let target_signature = document.with_view(|view| {
1154 let parent = if let Some(id) = self.start_node_id {
1155 signing_start_node(view.document(), id, self.id_attributes)?
1156 } else {
1157 view.document().root_element()
1158 };
1159 let appended = parent
1160 .children()
1161 .rfind(|node| node.has_tag_name((XMLDSIG_NS, "Signature")))
1162 .ok_or(SigningDigestError::MissingElement {
1163 element: "Signature",
1164 })?;
1165 signature_index(view.document(), appended).map_err(SigningError::from)
1166 })?;
1167 self.sign_template_at_index_with_budgets(document, target_signature, budgets)?;
1168 Ok(())
1169 }
1170}
1171
1172fn projected_signature_output_len(
1173 algorithm: SignatureAlgorithm,
1174 raw_signature_len: usize,
1175 encoding: crate::policy::EcdsaSignatureValueEncoding,
1176) -> Result<usize, SigningError> {
1177 if matches!(
1178 (algorithm, encoding),
1179 (
1180 SignatureAlgorithm::EcdsaSha256 | SignatureAlgorithm::EcdsaSha384,
1181 crate::policy::EcdsaSignatureValueEncoding::XmlSecAsn1Der
1182 )
1183 ) {
1184 return maximum_ecdsa_der_signature_len(raw_signature_len)
1185 .ok_or(SigningKeyError::InvalidPublicKeyInfo.into());
1186 }
1187 Ok(raw_signature_len)
1188}
1189
1190fn map_owned_document_mutation_error(error: XmlDocumentError) -> SigningError {
1191 match owned_document_policy_violation(error) {
1192 Ok(error) => SigningError::Policy(error),
1193 Err(error) => SigningError::Document(error),
1194 }
1195}
1196
1197fn map_owned_document_digest_mutation_error(error: XmlDocumentError) -> SigningDigestError {
1198 match owned_document_policy_violation(error) {
1199 Ok(error) => SigningDigestError::Policy(error),
1200 Err(error) => SigningDigestError::Document(error),
1201 }
1202}
1203
1204fn owned_document_policy_violation(
1205 error: XmlDocumentError,
1206) -> Result<crate::policy::PolicyViolation, XmlDocumentError> {
1207 match error {
1208 XmlDocumentError::Policy(error) => Ok(error),
1209 XmlDocumentError::DocumentTooLarge { maximum, actual } => {
1210 Ok(crate::policy::PolicyViolation::ResourceLimit {
1211 resource: crate::policy::resource_name::XML_DOCUMENT,
1212 maximum,
1213 actual,
1214 })
1215 }
1216 XmlDocumentError::DocumentTooDeep { maximum, actual } => {
1217 Ok(crate::policy::PolicyViolation::ResourceLimit {
1218 resource: crate::policy::resource_name::XML_DEPTH,
1219 maximum,
1220 actual,
1221 })
1222 }
1223 XmlDocumentError::ProjectedNodeLimit { maximum } => {
1224 Ok(crate::policy::PolicyViolation::ResourceLimit {
1225 resource: crate::policy::resource_name::XML_NODES,
1226 maximum,
1227 actual: maximum.saturating_add(1),
1228 })
1229 }
1230 error => Err(error),
1231 }
1232}
1233
1234fn encode_signature_output(
1235 algorithm: SignatureAlgorithm,
1236 signature: Vec<u8>,
1237 encoding: crate::policy::EcdsaSignatureValueEncoding,
1238) -> Result<Vec<u8>, SigningError> {
1239 if matches!(
1240 (algorithm, encoding),
1241 (
1242 SignatureAlgorithm::EcdsaSha256 | SignatureAlgorithm::EcdsaSha384,
1243 crate::policy::EcdsaSignatureValueEncoding::XmlSecAsn1Der
1244 )
1245 ) {
1246 return encode_ecdsa_signature_as_der(&signature)
1247 .ok_or(SigningKeyError::InvalidPublicKeyInfo.into());
1248 }
1249 Ok(signature)
1250}
1251
1252#[derive(Debug, Clone)]
1253struct SigningReference {
1254 uri: String,
1255 transforms: Vec<Transform>,
1256 digest_method: DigestAlgorithm,
1257 digest_value_range: Range<usize>,
1258 digest_value_node_id: NodeId,
1259}
1260
1261struct SigningOperationBudgets {
1262 transforms: TransformExecutionBudget,
1263 xpath_parse: XPathSignatureParseBudget,
1264}
1265
1266impl SigningOperationBudgets {
1267 fn from_resources(resources: &crate::policy::ResourcePolicy) -> Self {
1268 Self {
1269 transforms: TransformExecutionBudget::from_resources(resources),
1270 xpath_parse: XPathSignatureParseBudget::from_resources(resources),
1271 }
1272 }
1273}
1274
1275impl Default for SigningOperationBudgets {
1276 fn default() -> Self {
1277 Self::from_resources(&crate::policy::ResourcePolicy::default())
1278 }
1279}
1280
1281pub fn compute_reference_digest_values(
1288 xml: &str,
1289) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
1290 let execution_budget = TransformExecutionBudget::default();
1291 compute_reference_digest_values_with_options(
1292 xml,
1293 TransformOptions::default(),
1294 None,
1295 crate::provider::default_provider(),
1296 &execution_budget,
1297 None,
1298 &[],
1299 )
1300}
1301
1302fn compute_reference_digest_values_with_options(
1303 xml: &str,
1304 transform_options: TransformOptions,
1305 policy: Option<&crate::policy::SigningPolicy>,
1306 provider: &dyn crate::provider::CryptoProvider,
1307 execution_budget: &TransformExecutionBudget,
1308 target_signature: Option<usize>,
1309 id_attributes: &[crate::IdAttributeRegistration],
1310) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
1311 let doc = parse_signing_document(xml, policy, execution_budget.xml_parse_work())?;
1312 let signature = find_signing_signature_node(
1313 &doc,
1314 target_signature.map_or(SigningSignatureTarget::Last, SigningSignatureTarget::Index),
1315 )?;
1316 let signed_info = find_required_child(signature, "SignedInfo")?;
1317 let references = parse_signing_references(signed_info)?;
1318 validate_signing_references(&references, references.len(), policy)?;
1319 compute_signing_reference_digests(
1320 &doc,
1321 signature,
1322 references,
1323 transform_options,
1324 provider,
1325 execution_budget,
1326 SigningUriResolution {
1327 id_attributes,
1328 same_document_id_semantics: policy.map_or(
1329 crate::policy::SameDocumentIdSemantics::Specification,
1330 |policy| policy.transforms.same_document_id_semantics,
1331 ),
1332 },
1333 )
1334}
1335
1336fn fill_reference_digest_values_in_dependency_order(
1337 document: &mut XmlDocument,
1338 transform_options: TransformOptions,
1339 policy: &crate::policy::SigningPolicy,
1340 provider: &dyn crate::provider::CryptoProvider,
1341 budgets: &mut SigningOperationBudgets,
1342 target_signature: usize,
1343 id_attributes: &[crate::IdAttributeRegistration],
1344) -> Result<(), SigningDigestError> {
1345 let reference_limit = policy
1346 .resources
1347 .max_references
1348 .min(MAX_REFERENCES_PER_SIGNATURE);
1349 let process_manifests =
1350 policy.manifest_processing == crate::policy::ManifestProcessing::Process;
1351 let (signed_info_references, manifest_references) = document.with_view(|view| {
1352 let signature = find_signing_signature_node(
1353 view.document(),
1354 SigningSignatureTarget::Index(target_signature),
1355 )?;
1356 let signed_info = find_required_child(signature, "SignedInfo")?;
1357 let signed_info_references =
1358 parse_signing_references_with_budget(signed_info, &mut budgets.xpath_parse)?;
1359 validate_signing_references(
1360 &signed_info_references,
1361 signed_info_references.len(),
1362 Some(policy),
1363 )?;
1364 let manifest_references = if process_manifests {
1365 parse_signing_manifest_references(
1366 signature,
1367 &mut budgets.xpath_parse,
1368 reference_limit.saturating_sub(signed_info_references.len()),
1369 reference_limit,
1370 )?
1371 } else {
1372 Vec::new()
1373 };
1374 Ok::<_, SigningDigestError>((signed_info_references, manifest_references))
1375 })?;
1376 let total_references = signed_info_references
1377 .len()
1378 .checked_add(manifest_references.len())
1379 .ok_or_else(|| SigningDigestError::InvalidStructure("reference count overflow".into()))?;
1380 validate_signing_references(&manifest_references, total_references, Some(policy))?;
1381 let placeholder = "AA==";
1382 let analysis_replacements = document.with_view(|view| {
1386 let signature = find_signing_signature_node(
1387 view.document(),
1388 SigningSignatureTarget::Index(target_signature),
1389 )?;
1390 let signature_value = find_required_child(signature, "SignatureValue")?;
1391 let mut replacements = signed_info_references
1392 .iter()
1393 .chain(&manifest_references)
1394 .map(|reference| {
1395 (
1396 view.node_identity_by_id(reference.digest_value_node_id),
1397 placeholder.to_owned(),
1398 )
1399 })
1400 .collect::<Vec<_>>();
1401 replacements.push((view.node_identity(signature_value), placeholder.to_owned()));
1402 Ok::<_, SigningDigestError>(replacements)
1403 })?;
1404 let analysis_xml = document
1405 .project_base64_contents(
1406 &analysis_replacements,
1407 policy.resources.max_xml_document_bytes,
1408 )
1409 .map_err(map_owned_document_digest_mutation_error)?;
1410 let analysis_doc = parse_signing_document(
1411 &analysis_xml,
1412 Some(policy),
1413 budgets.transforms.xml_parse_work(),
1414 )?;
1415 let analysis_signature = find_signing_signature_node(
1416 &analysis_doc,
1417 SigningSignatureTarget::Index(target_signature),
1418 )?;
1419 let analysis_signed_info = find_required_child(analysis_signature, "SignedInfo")?;
1420 let mut analysis_references =
1421 parse_signing_references_with_budget(analysis_signed_info, &mut budgets.xpath_parse)?;
1422 if process_manifests {
1423 analysis_references.extend(parse_signing_manifest_references(
1424 analysis_signature,
1425 &mut budgets.xpath_parse,
1426 reference_limit.saturating_sub(signed_info_references.len()),
1427 reference_limit,
1428 )?);
1429 }
1430 let dependency_plan = reference_dependency_levels(
1431 &analysis_doc,
1432 analysis_signature,
1433 &analysis_references,
1434 transform_options,
1435 &budgets.transforms,
1436 id_attributes,
1437 policy.transforms.same_document_id_semantics,
1438 )?;
1439 for level in dependency_plan {
1440 let replacements = document.with_view(|view| {
1441 let current_doc = view.document();
1442 let current_signature = find_signing_signature_node(
1443 current_doc,
1444 SigningSignatureTarget::Index(target_signature),
1445 )?;
1446 let current_signed_info = find_required_child(current_signature, "SignedInfo")?;
1447 let current_signed_info_references = parse_signing_references_with_budget(
1448 current_signed_info,
1449 &mut budgets.xpath_parse,
1450 )?;
1451 let current_manifest_references = if process_manifests {
1452 parse_signing_manifest_references(
1453 current_signature,
1454 &mut budgets.xpath_parse,
1455 reference_limit.saturating_sub(signed_info_references.len()),
1456 reference_limit,
1457 )?
1458 } else {
1459 Vec::new()
1460 };
1461 if current_signed_info_references.len() != signed_info_references.len()
1462 || current_manifest_references.len() != manifest_references.len()
1463 {
1464 return Err(SigningDigestError::InvalidStructure(
1465 "signing Reference set changed while filling digests".into(),
1466 ));
1467 }
1468 let mut destinations = Vec::with_capacity(level.len());
1469 let mut level_references = Vec::with_capacity(level.len());
1470 for index in &level {
1471 let reference = if *index < signed_info_references.len() {
1472 current_signed_info_references.get(*index)
1473 } else {
1474 current_manifest_references.get(*index - signed_info_references.len())
1475 }
1476 .ok_or_else(|| {
1477 SigningDigestError::InvalidStructure(
1478 "signing Reference set changed while filling digests".into(),
1479 )
1480 })?;
1481 destinations.push(view.node_identity_by_id(reference.digest_value_node_id));
1482 level_references.push(reference.clone());
1483 }
1484 let computed = compute_signing_reference_digests(
1485 current_doc,
1486 current_signature,
1487 level_references,
1488 transform_options,
1489 provider,
1490 &budgets.transforms,
1491 SigningUriResolution {
1492 id_attributes,
1493 same_document_id_semantics: policy.transforms.same_document_id_semantics,
1494 },
1495 )?;
1496 if computed.len() != destinations.len() {
1497 return Err(SigningDigestError::InvalidStructure(
1498 "signing Reference set changed while computing digests".into(),
1499 ));
1500 }
1501 Ok::<_, SigningDigestError>(
1502 destinations
1503 .into_iter()
1504 .zip(computed)
1505 .map(|(target, digest)| (target, digest.digest_value))
1506 .collect::<Vec<_>>(),
1507 )
1508 })?;
1509 document
1510 .replace_base64_contents_with_budget(
1511 &replacements,
1512 DocumentParseSettings::from_policy(&policy.xml, &policy.resources),
1513 budgets.transforms.xml_parse_work(),
1514 )
1515 .map_err(map_owned_document_digest_mutation_error)?;
1516 }
1517 Ok(())
1518}
1519
1520fn reference_dependency_levels(
1521 doc: &Document<'_>,
1522 signature: Node<'_, '_>,
1523 references: &[SigningReference],
1524 transform_options: TransformOptions,
1525 execution_budget: &TransformExecutionBudget,
1526 id_attributes: &[crate::IdAttributeRegistration],
1527 same_document_id_semantics: crate::policy::SameDocumentIdSemantics,
1528) -> Result<Vec<Vec<usize>>, SigningDigestError> {
1529 let resolver = UriReferenceResolver::with_id_registrations(doc, id_attributes)
1530 .with_same_document_id_semantics(same_document_id_semantics);
1531 let terminal_signature_value_index = references.len();
1532 let signature_value = find_required_child(signature, "SignatureValue")?;
1533 let mut tracked_mutable_nodes = references
1534 .iter()
1535 .enumerate()
1536 .flat_map(|(index, reference)| {
1537 std::iter::once((index, reference.digest_value_node_id)).chain(
1538 doc.get_node(reference.digest_value_node_id)
1539 .into_iter()
1540 .flat_map(|node| node.children())
1541 .filter(|node| node.is_text())
1542 .map(move |node| (index, node.id())),
1543 )
1544 })
1545 .collect::<Vec<_>>();
1546 tracked_mutable_nodes.push((terminal_signature_value_index, signature_value.id()));
1547 tracked_mutable_nodes.extend(
1548 signature_value
1549 .children()
1550 .filter(|node| node.is_text())
1551 .map(|node| (terminal_signature_value_index, node.id())),
1552 );
1553 let analyses = references
1554 .iter()
1555 .map(|reference| {
1556 let initial_data = resolver.dereference_with_budget(
1557 &reference.uri,
1558 execution_budget.node_set_materialization(),
1559 )?;
1560 let output = execute_transforms_with_dependency_nodes(
1561 signature,
1562 initial_data,
1563 &reference.transforms,
1564 transform_options,
1565 execution_budget,
1566 tracked_mutable_nodes.clone(),
1567 )?;
1568 Ok(output.dependencies)
1569 })
1570 .collect::<Result<Vec<_>, SigningDigestError>>()?;
1571 if analyses
1572 .iter()
1573 .any(|dependencies| dependencies.contains(&terminal_signature_value_index))
1574 {
1575 return Err(SigningDigestError::InvalidStructure(
1576 "Reference dependency cycle includes the mutable SignatureValue".into(),
1577 ));
1578 }
1579 let mut dependencies = analyses;
1580 let mut completed = vec![false; references.len()];
1581 let mut levels = Vec::new();
1582 while completed.iter().any(|done| !done) {
1583 let ready = dependencies
1584 .iter()
1585 .enumerate()
1586 .filter_map(|(index, dependencies)| {
1587 (!completed[index] && dependencies.is_empty()).then_some(index)
1588 })
1589 .collect::<Vec<_>>();
1590 if ready.is_empty() {
1591 return Err(SigningDigestError::InvalidStructure(
1592 "Manifest Reference digest dependency cycle".into(),
1593 ));
1594 }
1595 for index in &ready {
1596 completed[*index] = true;
1597 }
1598 for dependency_set in &mut dependencies {
1599 dependency_set.retain(|dependency| !completed[*dependency]);
1600 }
1601 levels.push(ready);
1602 }
1603
1604 debug_assert!(references.iter().all(|reference| {
1608 signature.range().start <= reference.digest_value_range.start
1609 && signature.range().end >= reference.digest_value_range.end
1610 }));
1611 Ok(levels)
1612}
1613
1614fn validate_signing_references(
1615 references: &[SigningReference],
1616 total_references: usize,
1617 policy: Option<&crate::policy::SigningPolicy>,
1618) -> Result<(), SigningDigestError> {
1619 let Some(policy) = policy else {
1620 return Ok(());
1621 };
1622 if total_references > policy.resources.max_references {
1623 return Err(crate::policy::PolicyViolation::ResourceLimit {
1624 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
1625 maximum: policy.resources.max_references,
1626 actual: total_references,
1627 }
1628 .into());
1629 }
1630 for reference in references {
1631 validate_signing_reference_uri(&reference.uri, policy)?;
1632 if reference.transforms.len() > policy.resources.max_transforms_per_reference {
1633 return Err(crate::policy::PolicyViolation::ResourceLimit {
1634 resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
1635 maximum: policy.resources.max_transforms_per_reference,
1636 actual: reference.transforms.len(),
1637 }
1638 .into());
1639 }
1640 if policy
1641 .digest_algorithms
1642 .as_ref()
1643 .is_some_and(|allowed| !allowed.contains(&reference.digest_method))
1644 {
1645 return Err(crate::policy::PolicyViolation::Algorithm {
1646 operation: "signing",
1647 algorithm: reference.digest_method.uri().to_string(),
1648 }
1649 .into());
1650 }
1651 let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
1652 validate_signing_transform_policy(
1653 initial_binary,
1654 &reference.transforms,
1655 policy.transforms.allowed_algorithms.as_ref(),
1656 )?;
1657 }
1658 Ok(())
1659}
1660
1661struct SigningUriResolution<'a> {
1662 id_attributes: &'a [crate::IdAttributeRegistration],
1663 same_document_id_semantics: crate::policy::SameDocumentIdSemantics,
1664}
1665
1666fn compute_signing_reference_digests(
1667 doc: &Document<'_>,
1668 signature: Node<'_, '_>,
1669 references: Vec<SigningReference>,
1670 transform_options: TransformOptions,
1671 provider: &dyn crate::provider::CryptoProvider,
1672 execution_budget: &TransformExecutionBudget,
1673 uri_resolution: SigningUriResolution<'_>,
1674) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
1675 let resolver = UriReferenceResolver::with_id_registrations(doc, uri_resolution.id_attributes)
1676 .with_same_document_id_semantics(uri_resolution.same_document_id_semantics);
1677 references
1678 .into_iter()
1679 .enumerate()
1680 .map(|(index, reference)| {
1681 let initial_data = resolver.dereference_with_budget(
1682 &reference.uri,
1683 execution_budget.node_set_materialization(),
1684 )?;
1685 let pre_digest = execute_transforms_with_options_and_budget(
1686 signature,
1687 initial_data,
1688 &reference.transforms,
1689 transform_options,
1690 execution_budget,
1691 )?;
1692 let digest = super::compute_digest_with_provider(
1693 provider,
1694 reference.digest_method,
1695 &pre_digest,
1696 )?;
1697 let digest_value = base64::engine::general_purpose::STANDARD.encode(digest);
1698 Ok(ComputedReferenceDigest {
1699 index,
1700 uri: reference.uri,
1701 digest_method: reference.digest_method,
1702 digest_value,
1703 })
1704 })
1705 .collect()
1706}
1707
1708pub fn fill_reference_digest_values(xml: &str) -> Result<String, SigningDigestError> {
1715 let execution_budget = TransformExecutionBudget::default();
1716 fill_reference_digest_values_with_options(
1717 xml,
1718 TransformOptions::default(),
1719 None,
1720 crate::provider::default_provider(),
1721 &execution_budget,
1722 None,
1723 &[],
1724 )
1725}
1726
1727fn fill_reference_digest_values_with_options(
1728 xml: &str,
1729 transform_options: TransformOptions,
1730 policy: Option<&crate::policy::SigningPolicy>,
1731 provider: &dyn crate::provider::CryptoProvider,
1732 execution_budget: &TransformExecutionBudget,
1733 target_signature: Option<usize>,
1734 id_attributes: &[crate::IdAttributeRegistration],
1735) -> Result<String, SigningDigestError> {
1736 let digest_values = compute_reference_digest_values_with_options(
1737 xml,
1738 transform_options,
1739 policy,
1740 provider,
1741 execution_budget,
1742 target_signature,
1743 id_attributes,
1744 )?
1745 .into_iter()
1746 .map(|digest| digest.digest_value);
1747 Ok(if let Some(target_signature) = target_signature {
1748 fill_signed_info_digest_values_at_index_with_budget(
1749 xml,
1750 digest_values,
1751 target_signature,
1752 policy,
1753 Some(execution_budget.xml_parse_work()),
1754 )?
1755 } else if let Some(policy) = policy {
1756 fill_signed_info_digest_values_with_budget(
1757 xml,
1758 digest_values,
1759 Some(policy),
1760 Some(execution_budget.xml_parse_work()),
1761 )?
1762 } else {
1763 fill_signed_info_digest_values_with_budget(
1764 xml,
1765 digest_values,
1766 None,
1767 Some(execution_budget.xml_parse_work()),
1768 )?
1769 })
1770}
1771
1772fn canonicalize_signed_info(
1773 document: &XmlDocument,
1774 policy: &crate::policy::SigningPolicy,
1775 budgets: &mut SigningOperationBudgets,
1776 target_signature: usize,
1777) -> Result<(SignatureAlgorithm, Vec<u8>), SigningError> {
1778 document.with_view(|view| {
1779 let doc = view.document();
1780 let signature =
1781 find_signing_signature_node(doc, SigningSignatureTarget::Index(target_signature))
1782 .map_err(SigningError::Digest)?;
1783 let signed_info_node =
1784 find_required_child(signature, "SignedInfo").map_err(SigningError::Digest)?;
1785 let signed_info =
1786 parse_signed_info_with_xpath_budget(signed_info_node, &mut budgets.xpath_parse)?;
1787 if policy
1788 .transforms
1789 .allowed_algorithms
1790 .as_ref()
1791 .is_some_and(|allowed| !allowed.contains(signed_info.c14n_method.uri()))
1792 {
1793 return Err(crate::policy::PolicyViolation::Algorithm {
1794 operation: "SignedInfo canonicalization",
1795 algorithm: signed_info.c14n_method.uri().to_owned(),
1796 }
1797 .into());
1798 }
1799 let signed_info_subtree: HashSet<_> = signed_info_node
1800 .descendants()
1801 .map(|node: Node<'_, '_>| node.id())
1802 .collect();
1803 let mut canonical_signed_info = Vec::new();
1804 canonicalize_bounded_with_xml_base_budget(
1805 doc,
1806 Some(&|node| signed_info_subtree.contains(&node.id())),
1807 &signed_info.c14n_method,
1808 budgets.transforms.remaining_c14n_output(),
1809 budgets.transforms.xml_base_resolution(),
1810 &mut canonical_signed_info,
1811 )
1812 .map_err(|error| {
1813 if let Some(violation) = map_c14n_resource_policy_violation(
1814 &error,
1815 crate::policy::resource_name::CANONICALIZED_BYTES,
1816 budgets.transforms.c14n_output_limit(),
1817 ) {
1818 SigningError::Policy(violation)
1819 } else {
1820 SigningError::Canonicalization(error)
1821 }
1822 })?;
1823 Ok((signed_info.signature_method, canonical_signed_info))
1824 })
1825}
1826
1827fn parse_signing_document<'a>(
1828 xml: &'a str,
1829 policy: Option<&crate::policy::SigningPolicy>,
1830 budget: &XmlParseWorkBudget,
1831) -> Result<Document<'a>, SigningDigestError> {
1832 let settings = policy
1833 .map(|policy| DocumentParseSettings::from_policy(&policy.xml, &policy.resources))
1834 .unwrap_or_default();
1835 super::mutation::parse_with_options_and_budget(xml, settings, Some(budget)).map_err(|error| {
1836 match error.into_policy_violation(settings) {
1837 Ok(error) => SigningDigestError::Policy(error),
1838 Err(XmlDocumentError::Parse(error)) => SigningDigestError::XmlParse(error),
1839 Err(error) => SigningDigestError::Document(error),
1840 }
1841 })
1842}
1843
1844fn parse_private_key_pem(private_key_pem: &str) -> Result<Vec<u8>, SigningKeyError> {
1845 let (rest, pem) = x509_parser::pem::parse_x509_pem(private_key_pem.as_bytes())
1846 .map_err(|_| SigningKeyError::InvalidKeyPem)?;
1847 if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
1848 return Err(SigningKeyError::InvalidKeyPem);
1849 }
1850 if pem.label != "PRIVATE KEY" {
1851 return Err(SigningKeyError::InvalidKeyFormat { label: pem.label });
1852 }
1853 Ok(pem.contents)
1854}
1855
1856enum SigningSignatureTarget {
1857 First,
1858 Last,
1859 Index(usize),
1860}
1861
1862fn find_signing_signature_node<'a>(
1863 doc: &'a Document<'a>,
1864 target: SigningSignatureTarget,
1865) -> Result<Node<'a, 'a>, SigningDigestError> {
1866 let mut signatures = doc.descendants().filter(|node| {
1867 node.is_element()
1868 && node.tag_name().name() == "Signature"
1869 && node.tag_name().namespace() == Some(XMLDSIG_NS)
1870 });
1871 match target {
1872 SigningSignatureTarget::First => signatures.next(),
1873 SigningSignatureTarget::Last => signatures.next_back(),
1874 SigningSignatureTarget::Index(index) => signatures.nth(index),
1875 }
1876 .ok_or(SigningDigestError::MissingElement {
1877 element: "Signature",
1878 })
1879}
1880
1881fn signing_signature_index(
1882 doc: &Document<'_>,
1883 start_node_id: Option<&str>,
1884 id_attributes: &[crate::IdAttributeRegistration],
1885 selection: SignatureTemplateSelection,
1886) -> Result<usize, SigningDigestError> {
1887 let selected = if let Some(id) = start_node_id {
1888 let start = signing_start_node(doc, id, id_attributes)?;
1889 let mut signatures = start
1890 .descendants()
1891 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Signature")));
1892 match selection.target() {
1893 SigningSignatureTarget::First => signatures.next(),
1894 SigningSignatureTarget::Last => signatures.next_back(),
1895 SigningSignatureTarget::Index(_) => unreachable!("public selection is not indexed"),
1896 }
1897 .ok_or_else(|| {
1898 SigningDigestError::InvalidStructure(format!(
1899 "selected node subtree has no Signature: {id}"
1900 ))
1901 })?
1902 } else {
1903 find_signing_signature_node(doc, selection.target())?
1904 };
1905 signature_index(doc, selected)
1906}
1907
1908fn signing_start_node<'a>(
1909 doc: &'a Document<'a>,
1910 id: &str,
1911 id_attributes: &[crate::IdAttributeRegistration],
1912) -> Result<Node<'a, 'a>, SigningDigestError> {
1913 UriReferenceResolver::with_id_registrations(doc, id_attributes)
1914 .node_for_id(id)
1915 .ok_or_else(|| {
1916 SigningDigestError::InvalidStructure(format!(
1917 "selected node ID is missing or ambiguous: {id}"
1918 ))
1919 })
1920}
1921
1922fn signature_index(
1923 doc: &Document<'_>,
1924 selected: Node<'_, '_>,
1925) -> Result<usize, SigningDigestError> {
1926 doc.descendants()
1927 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Signature")))
1928 .position(|node| node == selected)
1929 .ok_or(SigningDigestError::MissingElement {
1930 element: "Signature",
1931 })
1932}
1933
1934fn parse_signing_references(
1935 signed_info: Node<'_, '_>,
1936) -> Result<Vec<SigningReference>, SigningDigestError> {
1937 parse_signing_references_with_budget(signed_info, &mut XPathSignatureParseBudget::default())
1938}
1939
1940fn parse_signing_references_with_budget(
1941 signed_info: Node<'_, '_>,
1942 xpath_budget: &mut XPathSignatureParseBudget,
1943) -> Result<Vec<SigningReference>, SigningDigestError> {
1944 verify_ds_element(signed_info, "SignedInfo")?;
1945 let mut children = element_children(signed_info);
1946
1947 let c14n_node = children.next().ok_or(SigningDigestError::MissingElement {
1948 element: "CanonicalizationMethod",
1949 })?;
1950 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
1951 required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
1952
1953 let signature_method_node = children.next().ok_or(SigningDigestError::MissingElement {
1954 element: "SignatureMethod",
1955 })?;
1956 verify_ds_element(signature_method_node, "SignatureMethod")?;
1957 required_algorithm_attr(signature_method_node, "SignatureMethod")?;
1958
1959 let mut references = Vec::new();
1960 for child in children {
1961 verify_ds_element(child, "Reference")?;
1962 if references.len() == MAX_REFERENCES_PER_SIGNATURE {
1963 return Err(SigningDigestError::InvalidStructure(format!(
1964 "SignedInfo contains more than {MAX_REFERENCES_PER_SIGNATURE} Reference elements"
1965 )));
1966 }
1967 references.push(parse_signing_reference(child, xpath_budget)?);
1968 }
1969 if references.is_empty() {
1970 return Err(SigningDigestError::MissingElement {
1971 element: "Reference",
1972 });
1973 }
1974 Ok(references)
1975}
1976
1977fn parse_signing_manifest_references(
1978 signature: Node<'_, '_>,
1979 xpath_budget: &mut XPathSignatureParseBudget,
1980 mut remaining_capacity: usize,
1981 maximum_references: usize,
1982) -> Result<Vec<SigningReference>, SigningDigestError> {
1983 let mut references = Vec::new();
1984 for manifest in signature
1985 .children()
1986 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Object")))
1987 .flat_map(|object| {
1988 object
1989 .children()
1990 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Manifest")))
1991 })
1992 {
1993 let mut manifest_references = 0usize;
1994 for child in element_children(manifest) {
1995 verify_ds_element(child, "Reference")?;
1996 if remaining_capacity == 0 {
1997 return Err(crate::policy::PolicyViolation::ResourceLimit {
1998 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
1999 maximum: maximum_references,
2000 actual: maximum_references.saturating_add(1),
2001 }
2002 .into());
2003 }
2004 remaining_capacity -= 1;
2005 references.push(parse_signing_reference(child, xpath_budget)?);
2006 manifest_references += 1;
2007 }
2008 if manifest_references == 0 {
2009 return Err(SigningDigestError::MissingElement {
2010 element: "Reference",
2011 });
2012 }
2013 }
2014 Ok(references)
2015}
2016
2017fn parse_signing_reference(
2018 reference_node: Node<'_, '_>,
2019 xpath_budget: &mut XPathSignatureParseBudget,
2020) -> Result<SigningReference, SigningDigestError> {
2021 let uri = reference_node
2022 .attribute("URI")
2023 .ok_or_else(|| {
2024 SigningDigestError::InvalidStructure(
2025 "signing Reference must include URI attribute".into(),
2026 )
2027 })?
2028 .to_string();
2029 let mut children = element_children(reference_node);
2030
2031 let mut transforms = Vec::new();
2032 let mut next = children.next().ok_or(SigningDigestError::MissingElement {
2033 element: "DigestMethod",
2034 })?;
2035 if next.tag_name().name() == "Transforms" && next.tag_name().namespace() == Some(XMLDSIG_NS) {
2036 transforms = parse_transforms_with_budget(next, xpath_budget)?;
2037 next = children.next().ok_or(SigningDigestError::MissingElement {
2038 element: "DigestMethod",
2039 })?;
2040 }
2041
2042 verify_ds_element(next, "DigestMethod")?;
2043 let digest_uri = required_algorithm_attr(next, "DigestMethod")?;
2044 let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
2045 SigningDigestError::UnsupportedAlgorithm {
2046 uri: digest_uri.to_string(),
2047 }
2048 })?;
2049 if !digest_method.signing_allowed() {
2050 return Err(SigningDigestError::SigningAlgorithmDisabled {
2051 uri: digest_method.uri(),
2052 });
2053 }
2054
2055 let digest_value_node = children.next().ok_or(SigningDigestError::MissingElement {
2056 element: "DigestValue",
2057 })?;
2058 verify_ds_element(digest_value_node, "DigestValue")?;
2059
2060 if let Some(unexpected) = children.next() {
2061 return Err(SigningDigestError::InvalidStructure(format!(
2062 "unexpected element <{}> after <DigestValue> in <Reference>",
2063 unexpected.tag_name().name()
2064 )));
2065 }
2066
2067 Ok(SigningReference {
2068 uri,
2069 transforms,
2070 digest_method,
2071 digest_value_range: digest_value_node.range(),
2072 digest_value_node_id: digest_value_node.id(),
2073 })
2074}
2075
2076fn find_required_child<'a>(
2077 parent: Node<'a, 'a>,
2078 child_name: &'static str,
2079) -> Result<Node<'a, 'a>, SigningDigestError> {
2080 parent
2081 .children()
2082 .find(|node| {
2083 node.is_element()
2084 && node.tag_name().name() == child_name
2085 && node.tag_name().namespace() == Some(XMLDSIG_NS)
2086 })
2087 .ok_or(SigningDigestError::MissingElement {
2088 element: child_name,
2089 })
2090}
2091
2092fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
2093 node.children().filter(Node::is_element)
2094}
2095
2096fn verify_ds_element(
2097 node: Node<'_, '_>,
2098 expected_name: &'static str,
2099) -> Result<(), SigningDigestError> {
2100 if !node.is_element() {
2101 return Err(SigningDigestError::InvalidStructure(format!(
2102 "expected element <{expected_name}>, got non-element node"
2103 )));
2104 }
2105 let tag = node.tag_name();
2106 if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
2107 return Err(SigningDigestError::InvalidStructure(format!(
2108 "expected <ds:{expected_name}>, got <{}>",
2109 tag.name()
2110 )));
2111 }
2112 Ok(())
2113}
2114
2115fn required_algorithm_attr<'a>(
2116 node: Node<'a, 'a>,
2117 element_name: &'static str,
2118) -> Result<&'a str, SigningDigestError> {
2119 node.attribute("Algorithm").ok_or_else(|| {
2120 SigningDigestError::InvalidStructure(format!(
2121 "missing Algorithm attribute on <{element_name}>"
2122 ))
2123 })
2124}
2125
2126#[cfg(test)]
2127mod error_conversion_tests {
2128 use super::*;
2129 use crate::policy::PolicyViolation;
2130
2131 struct RejectingSigningKey;
2132
2133 impl SigningKey for RejectingSigningKey {
2134 fn sign(
2135 &self,
2136 _algorithm: SignatureAlgorithm,
2137 _canonical_signed_info: &[u8],
2138 ) -> Result<Vec<u8>, SigningKeyError> {
2139 Err(SigningKeyError::SigningFailed)
2140 }
2141
2142 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
2143 Err(SigningKeyError::PublicKeyEncodingFailed)
2144 }
2145 }
2146
2147 struct FixedRsaSigningKey;
2148
2149 impl SigningKey for FixedRsaSigningKey {
2150 fn sign(
2151 &self,
2152 _algorithm: SignatureAlgorithm,
2153 _canonical_signed_info: &[u8],
2154 ) -> Result<Vec<u8>, SigningKeyError> {
2155 Ok(vec![0x5a; 256])
2156 }
2157
2158 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
2159 Ok(SigningPublicKeyInfo::Rsa {
2160 spki_der: Vec::new(),
2161 modulus: vec![0x80; 256],
2162 exponent: vec![1, 0, 1],
2163 })
2164 }
2165 }
2166
2167 #[test]
2168 fn signing_error_promotes_every_policy_failure() {
2169 let digest = SigningError::from(SigningDigestError::Policy(PolicyViolation::Algorithm {
2172 operation: "signing",
2173 algorithm: "urn:test:digest".into(),
2174 }));
2175 assert!(matches!(digest, SigningError::Policy(_)));
2176
2177 let mutation = SigningError::from(SigningDigestError::XmlMutation(
2178 XmlMutationError::Policy(PolicyViolation::ResourceLimit {
2179 resource: crate::policy::resource_name::XML_DOCUMENT,
2180 maximum: 1,
2181 actual: 2,
2182 }),
2183 ));
2184 assert!(matches!(mutation, SigningError::Policy(_)));
2185 }
2186
2187 #[test]
2188 fn manifest_reparse_consumes_the_signature_wide_xpath_budget() {
2189 let filter = r#"<xf:XPath xmlns:xf="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</xf:XPath>"#;
2193 let signed_info_transforms = format!(
2194 r#"<ds:Transform Algorithm="http://www.w3.org/2002/06/xmldsig-filter2">{}</ds:Transform><ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>true()</ds:XPath></ds:Transform><ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>true()</ds:XPath></ds:Transform>"#,
2195 filter.repeat(64)
2196 );
2197 let signed_info_references = (0..62)
2198 .map(|index| {
2199 let extra = if index == 0 {
2200 r#"<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>true()</ds:XPath></ds:Transform>"#
2201 } else {
2202 ""
2203 };
2204 format!(
2205 r##"<ds:Reference URI="#payload"><ds:Transforms>{signed_info_transforms}{extra}</ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference>"##
2206 )
2207 })
2208 .collect::<String>();
2209 let manifest_reference = |id: &str| {
2210 format!(
2211 r##"<ds:Reference URI="#{id}"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>true()</ds:XPath></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference>"##
2212 )
2213 };
2214 let xml = format!(
2215 r##"<root><payload Id="payload"/><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>{signed_info_references}</ds:SignedInfo><ds:SignatureValue/><ds:Object><ds:Manifest>{}{}</ds:Manifest></ds:Object></ds:Signature></root>"##,
2216 manifest_reference("payload"),
2217 manifest_reference("payload")
2218 );
2219 let policy = crate::policy::SigningPolicy {
2220 manifest_processing: crate::policy::ManifestProcessing::Process,
2221 ..crate::policy::SigningPolicy::default()
2222 };
2223
2224 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
2225 let error = fill_reference_digest_values_in_dependency_order(
2226 &mut document,
2227 TransformOptions::default(),
2228 &policy,
2229 crate::provider::default_provider(),
2230 &mut SigningOperationBudgets::default(),
2231 0,
2232 &[],
2233 )
2234 .expect_err("Manifest reparse must not reset the XPath parse budget");
2235
2236 assert!(
2237 matches!(
2238 &error,
2239 SigningDigestError::Transform(TransformError::Policy(
2240 crate::policy::PolicyViolation::ResourceLimit {
2241 resource: "XPath expressions",
2242 ..
2243 }
2244 ))
2245 ),
2246 "expected the shared XPath budget error, got: {error:?}"
2247 );
2248 }
2249
2250 #[test]
2251 fn dependency_levels_share_the_xml_parse_work_budget() {
2252 let xml = r##"<root><payload Id="payload">nested payload</payload><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#outer"><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/><ds:Object><ds:Manifest Id="outer"><ds:Reference URI="#inner"><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:Manifest><ds:Manifest Id="inner"><ds:Reference URI="#payload"><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:Manifest></ds:Object></ds:Signature></root>"##;
2256 let policy = crate::policy::SigningPolicy {
2257 manifest_processing: crate::policy::ManifestProcessing::Process,
2258 ..crate::policy::SigningPolicy::default()
2259 };
2260 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
2261 let mut budgets = SigningOperationBudgets::from_resources(&policy.resources);
2262
2263 fill_reference_digest_values_in_dependency_order(
2264 &mut document,
2265 TransformOptions::default(),
2266 &policy,
2267 crate::provider::default_provider(),
2268 &mut budgets,
2269 0,
2270 &[],
2271 )
2272 .expect("the default cumulative budget must cover nested dependencies");
2273 let consumed = budgets.transforms.xml_parse_work().consumed();
2274 assert!(
2275 consumed > xml.len().saturating_mul(6),
2276 "analysis and dependency reparses must all be charged"
2277 );
2278
2279 let mut constrained_policy = policy;
2280 constrained_policy.resources.max_xml_parse_work_bytes = consumed - 1;
2281 let mut constrained_document = XmlDocument::parse(xml).expect("fixture must parse");
2282 let mut constrained_budgets =
2283 SigningOperationBudgets::from_resources(&constrained_policy.resources);
2284 let error = fill_reference_digest_values_in_dependency_order(
2285 &mut constrained_document,
2286 TransformOptions::default(),
2287 &constrained_policy,
2288 crate::provider::default_provider(),
2289 &mut constrained_budgets,
2290 0,
2291 &[],
2292 )
2293 .expect_err("one byte below measured work must fail closed");
2294
2295 assert!(matches!(
2296 error,
2297 SigningDigestError::Policy(PolicyViolation::ResourceLimit {
2298 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
2299 maximum,
2300 actual,
2301 }) if maximum == consumed - 1 && actual >= consumed
2302 ));
2303 }
2304
2305 #[test]
2306 fn final_signed_info_parse_consumes_the_signing_xpath_budget() {
2307 let xml = r##"<root><payload Id="payload">content</payload><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#payload"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>true()</ds:XPath></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/></ds:Signature></root>"##;
2311 let mut policy = crate::policy::SigningPolicy::default();
2312 policy.resources.max_xpath_expressions = 3;
2313
2314 let error = SignContext::new(&RejectingSigningKey)
2315 .policy(policy)
2316 .sign_template(xml)
2317 .expect_err("the final SignedInfo parse must not reset the XPath budget");
2318
2319 assert!(
2320 matches!(
2321 error,
2322 SigningError::Policy(PolicyViolation::ResourceLimit {
2323 resource: crate::policy::resource_name::XPATH_EXPRESSIONS,
2324 maximum: 3,
2325 ..
2326 })
2327 ),
2328 "expected the shared XPath parse budget error, got: {error:?}"
2329 );
2330 }
2331
2332 #[test]
2333 fn signing_initial_parse_consumes_the_operation_xml_parse_budget() {
2334 let xml = r##"<root><payload Id="payload"/><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#payload"><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/></ds:Signature></root>"##;
2338 let mut policy = crate::policy::SigningPolicy::default();
2339 policy.resources.max_xml_parse_work_bytes = 0;
2340
2341 let error = SignContext::new(&RejectingSigningKey)
2342 .policy(policy)
2343 .sign_template(xml)
2344 .expect_err("a zero parse-work budget must reject the initial parse");
2345
2346 assert!(matches!(
2347 error,
2348 SigningError::Policy(PolicyViolation::ResourceLimit {
2349 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
2350 maximum: 0,
2351 actual,
2352 }) if actual == xml.len()
2353 ));
2354 }
2355
2356 #[test]
2357 fn signing_string_entry_point_enforces_policy_depth() {
2358 let mut policy = crate::policy::SigningPolicy::default();
2361 policy.resources.max_xml_depth = 2;
2362 let xml = "<root><child><leaf/></child></root>";
2363
2364 assert!(matches!(
2365 SignContext::new(&RejectingSigningKey)
2366 .policy(policy)
2367 .sign_template(xml),
2368 Err(SigningError::Policy(PolicyViolation::ResourceLimit {
2369 resource: crate::policy::resource_name::XML_DEPTH,
2370 maximum: 2,
2371 actual: 3,
2372 }))
2373 ));
2374 }
2375
2376 #[test]
2377 fn builder_append_reports_policy_depth() {
2378 let mut policy = crate::policy::SigningPolicy::default();
2381 policy.resources.max_xml_depth = 4;
2382 let builder = SignatureBuilder::new(
2383 crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
2384 SignatureAlgorithm::RsaSha256,
2385 )
2386 .add_reference(crate::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256).uri(""));
2387
2388 let result = SignContext::new(&FixedRsaSigningKey)
2389 .policy(policy)
2390 .sign_with_builder("<root/>", &builder);
2391 assert!(
2392 matches!(
2393 result,
2394 Err(SigningError::Policy(PolicyViolation::ResourceLimit {
2395 resource: crate::policy::resource_name::XML_DEPTH,
2396 maximum: 4,
2397 actual: 5,
2398 }))
2399 ),
2400 "unexpected builder depth result: {result:?}"
2401 );
2402 }
2403
2404 #[test]
2405 fn owned_signing_staged_copies_preserve_policy_errors() {
2406 let mut policy = crate::policy::SigningPolicy::default();
2409 policy.resources.max_xml_parse_work_bytes = 0;
2410 let context = SignContext::new(&RejectingSigningKey).policy(policy);
2411
2412 let mut template_document = XmlDocument::parse("<root/>").expect("fixture must parse");
2413 let template_before = template_document.as_xml().to_owned();
2414 let error = context
2415 .sign_document(&mut template_document)
2416 .expect_err("the staged template copy must exhaust the operation budget");
2417 assert!(matches!(
2418 error,
2419 SigningError::Policy(PolicyViolation::ResourceLimit {
2420 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
2421 maximum: 0,
2422 actual,
2423 }) if actual == template_before.len()
2424 ));
2425 assert_eq!(template_document.as_xml(), template_before);
2426 assert_eq!(template_document.generation(), 0);
2427
2428 let mut builder_document = XmlDocument::parse("<root/>").expect("fixture must parse");
2429 let builder_before = builder_document.as_xml().to_owned();
2430 let builder = SignatureBuilder::new(
2431 crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
2432 SignatureAlgorithm::RsaSha256,
2433 );
2434 let error = context
2435 .sign_document_with_builder(&mut builder_document, &builder)
2436 .expect_err("the staged builder copy must exhaust the operation budget");
2437 assert!(matches!(
2438 error,
2439 SigningError::Policy(PolicyViolation::ResourceLimit {
2440 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
2441 maximum: 0,
2442 actual,
2443 }) if actual == builder_before.len()
2444 ));
2445 assert_eq!(builder_document.as_xml(), builder_before);
2446 assert_eq!(builder_document.generation(), 0);
2447 }
2448
2449 #[test]
2450 fn owned_signing_commits_the_validated_stage_without_reparsing() {
2451 let xml = r##"<root><payload Id="payload">content</payload><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#payload"><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/></ds:Signature></root>"##;
2455 let policy = crate::policy::SigningPolicy::default();
2456 let context = SignContext::new(&FixedRsaSigningKey).policy(policy.clone());
2457 let source = XmlDocument::parse(xml).expect("fixture must parse");
2458 let mut measured = SigningOperationBudgets::from_resources(&policy.resources);
2459 let mut staged = source
2460 .staged_copy_with_budget(
2461 DocumentParseSettings::from_policy(&policy.xml, &policy.resources),
2462 measured.transforms.xml_parse_work(),
2463 )
2464 .expect("staging must parse");
2465 context
2466 .sign_document_in_place(&mut staged, &mut measured)
2467 .expect("staged signing must succeed");
2468 let exact_stage_work = measured.transforms.xml_parse_work().consumed();
2469
2470 let mut constrained_policy = policy;
2471 constrained_policy.resources.max_xml_parse_work_bytes = exact_stage_work;
2472 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
2473 SignContext::new(&FixedRsaSigningKey)
2474 .policy(constrained_policy)
2475 .sign_document(&mut document)
2476 .expect("commit must not parse the validated stage again");
2477
2478 assert_eq!(document.generation(), 1);
2479 assert!(!document.as_xml().contains("<ds:DigestValue/>"));
2480 assert!(!document.as_xml().contains("<ds:SignatureValue/>"));
2481 }
2482
2483 #[test]
2484 fn builder_signing_fits_the_document_to_parse_work_ratio() {
2485 let padding = "x".repeat(64 * 1024);
2490 let xml = format!("<root><payload Id=\"payload\"/><padding>{padding}</padding></root>");
2491 let builder = SignatureBuilder::new(
2492 crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
2493 SignatureAlgorithm::RsaSha256,
2494 )
2495 .add_reference(
2496 crate::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256).uri("#payload"),
2497 );
2498 let maximum_document_bytes = xml.len() + 4 * 1024;
2499 let mut policy = crate::policy::SigningPolicy::default();
2500 policy.resources.max_xml_document_bytes = maximum_document_bytes;
2501 policy.resources.max_xml_parse_work_bytes = maximum_document_bytes * 16;
2502
2503 let mut measurement_policy = policy.clone();
2504 measurement_policy.resources.max_xml_parse_work_bytes =
2505 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING;
2506 let measurement_context =
2507 SignContext::new(&FixedRsaSigningKey).policy(measurement_policy.clone());
2508 let mut measurement_budgets =
2509 SigningOperationBudgets::from_resources(&measurement_policy.resources);
2510 let mut measurement_document = XmlDocument::parse_with_settings_and_budget(
2511 xml.clone(),
2512 DocumentParseSettings::from_policy(
2513 &measurement_policy.xml,
2514 &measurement_policy.resources,
2515 ),
2516 measurement_budgets.transforms.xml_parse_work(),
2517 )
2518 .expect("measurement input must parse");
2519 measurement_context
2520 .sign_document_with_builder_in_place(
2521 &mut measurement_document,
2522 &builder,
2523 &mut measurement_budgets,
2524 )
2525 .expect("measurement signing must succeed");
2526 let consumed = measurement_budgets.transforms.xml_parse_work().consumed();
2527 assert!(
2528 consumed <= maximum_document_bytes * 16,
2529 "builder signing consumed {consumed} bytes for a {maximum_document_bytes}-byte ceiling"
2530 );
2531
2532 let signed = SignContext::new(&FixedRsaSigningKey)
2533 .policy(policy.clone())
2534 .sign_with_builder(&xml, &builder)
2535 .expect("string builder signing must fit the advertised parse-work ratio");
2536 assert!(signed.contains("DigestValue>"));
2537 assert!(signed.contains("SignatureValue>"));
2538
2539 let mut owned = XmlDocument::parse(&xml).expect("fixture must parse");
2540 SignContext::new(&FixedRsaSigningKey)
2541 .policy(policy)
2542 .sign_document_with_builder(&mut owned, &builder)
2543 .expect("owned builder signing must fit the advertised parse-work ratio");
2544 assert!(owned.as_xml().contains("DigestValue>"));
2545 assert!(owned.as_xml().contains("SignatureValue>"));
2546 }
2547
2548 #[test]
2549 fn dtd_capable_staged_copy_charges_both_parser_passes() {
2550 let xml = "<root/>";
2553 let mut parsing_policy = crate::policy::SigningPolicy::default();
2554 parsing_policy.xml.allow_internal_dtd = true;
2555 let document = XmlDocument::parse_with_policy(xml, &parsing_policy)
2556 .expect("the fixture must retain DTD-capable parse settings");
2557
2558 parsing_policy.resources.max_xml_parse_work_bytes = xml.len();
2559 let budget = XmlParseWorkBudget::from_resources(&parsing_policy.resources);
2560 assert!(matches!(
2561 document.staged_copy_with_budget(DocumentParseSettings::default(), &budget),
2562 Err(XmlDocumentError::Policy(PolicyViolation::ResourceLimit {
2563 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
2564 maximum,
2565 actual,
2566 })) if maximum == xml.len() && actual == xml.len() * 2
2567 ));
2568 }
2569
2570 #[test]
2571 fn owned_signing_mappers_preserve_document_size_policy_errors() {
2572 let maximum = 8;
2575 let actual = 9;
2576 assert!(matches!(
2577 map_owned_document_mutation_error(XmlDocumentError::DocumentTooLarge {
2578 maximum,
2579 actual,
2580 }),
2581 SigningError::Policy(PolicyViolation::ResourceLimit {
2582 resource: crate::policy::resource_name::XML_DOCUMENT,
2583 maximum: 8,
2584 actual: 9,
2585 })
2586 ));
2587 assert!(matches!(
2588 map_owned_document_digest_mutation_error(XmlDocumentError::DocumentTooLarge {
2589 maximum,
2590 actual,
2591 }),
2592 SigningDigestError::Policy(PolicyViolation::ResourceLimit {
2593 resource: crate::policy::resource_name::XML_DOCUMENT,
2594 maximum: 8,
2595 actual: 9,
2596 })
2597 ));
2598 }
2599
2600 #[test]
2601 fn signing_rejects_xpath_control_dependencies_on_digest_values() {
2602 let xml = r##"<root><payload Id="payload">content</payload><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#payload"><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference><ds:Reference URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>not(ancestor-or-self::ds:DigestValue) and (not(self::payload) or string(//ds:Reference[1]/ds:DigestValue) = '')</ds:XPath></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/></ds:Signature></root>"##;
2606
2607 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
2608 let error = fill_reference_digest_values_in_dependency_order(
2609 &mut document,
2610 TransformOptions::default(),
2611 &crate::policy::SigningPolicy::default(),
2612 crate::provider::default_provider(),
2613 &mut SigningOperationBudgets::default(),
2614 0,
2615 &[],
2616 )
2617 .expect_err("mutable XPath control dependencies must fail closed");
2618
2619 assert!(
2620 matches!(
2621 &error,
2622 SigningDigestError::InvalidStructure(message)
2623 if message.contains("dependency cycle")
2624 ),
2625 "expected a dependency-cycle rejection, got: {error:?}"
2626 );
2627 }
2628
2629 #[test]
2630 fn signing_allows_payload_local_xpath_value_predicates() {
2631 let xml = r##"<root><payload Id="payload" kind="include">content</payload><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#payload"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>@kind = 'include'</ds:XPath></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/></ds:Signature></root>"##;
2634
2635 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
2636 fill_reference_digest_values_in_dependency_order(
2637 &mut document,
2638 TransformOptions::default(),
2639 &crate::policy::SigningPolicy::default(),
2640 crate::provider::default_provider(),
2641 &mut SigningOperationBudgets::default(),
2642 0,
2643 &[],
2644 )
2645 .expect("payload-local XPath predicates must not depend on Signature values");
2646
2647 assert_ne!(document.as_xml(), xml);
2648 }
2649
2650 #[test]
2651 fn signing_rejects_references_that_retain_signature_value() {
2652 let xml = r##"<root><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>not(ancestor-or-self::ds:DigestValue)</ds:XPath></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/></ds:Signature></root>"##;
2656
2657 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
2658 let error = fill_reference_digest_values_in_dependency_order(
2659 &mut document,
2660 TransformOptions::default(),
2661 &crate::policy::SigningPolicy::default(),
2662 crate::provider::default_provider(),
2663 &mut SigningOperationBudgets::default(),
2664 0,
2665 &[],
2666 )
2667 .expect_err("a mutable SignatureValue dependency must fail before signing");
2668
2669 assert!(
2670 matches!(
2671 &error,
2672 SigningDigestError::InvalidStructure(message)
2673 if message.contains("SignatureValue") && message.contains("cycle")
2674 ),
2675 "expected a SignatureValue dependency-cycle rejection, got: {error:?}"
2676 );
2677 }
2678}