Skip to main content

xml_sec/xmldsig/
sign.rs

1//! Signing-side XMLDSig digest computation.
2//!
3//! This pass fills `<DigestValue>` elements before `<SignedInfo>` is
4//! canonicalized and signed. It intentionally uses a signing-template parser
5//! instead of [`crate::xmldsig::parse::parse_signed_info`], because verification
6//! must continue to reject empty or malformed stored digest values.
7
8use crate::xml::dom::{Document, Node, NodeId};
9use base64::Engine;
10use hmac::{KeyInit, Mac};
11use p256::ecdsa::{
12    Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey,
13};
14use p256::pkcs8::{DecodePrivateKey, EncodePublicKey};
15use p384::ecdsa::{
16    Signature as P384Signature, SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey,
17};
18use p521::ecdsa::{
19    Signature as P521Signature, SigningKey as P521SigningKey, VerifyingKey as P521VerifyingKey,
20};
21use rsa::RsaPrivateKey;
22use rsa::pkcs1v15::Signature as RsaPkcs1v15Signature;
23use rsa::pkcs1v15::SigningKey as RsaPkcs1v15SigningKey;
24use rsa::signature::{RandomizedSigner, SignatureEncoding};
25use rsa::traits::PublicKeyParts;
26use sha1::Sha1;
27use sha2::{Sha224, Sha256, Sha384, Sha512};
28use signature::hazmat::{PrehashSigner, RandomizedPrehashSigner};
29use std::{
30    borrow::Cow,
31    collections::{HashMap, HashSet},
32    ops::Range,
33};
34use x509_parser::prelude::FromDer;
35use zeroize::Zeroizing;
36
37use crate::c14n::canonicalize_bounded_with_xml_base_budget;
38use crate::operation::{
39    OperationExecutionContext, OperationNodeId, OperationNodeKind, OperationPlanError,
40    OperationStage,
41};
42
43use super::builder::{SignatureBuilder, SignatureBuilderError};
44use super::digest::DigestAlgorithm;
45use super::mutation::{
46    XmlMutationError, fill_signed_info_digest_values_at_index_with_budget,
47    merge_key_info_source_at_index_with_budget, padded_base64_len_for_xml,
48};
49use super::parse::{
50    EC_P256_OID, EC_P384_OID, EC_P521_OID, MAX_REFERENCES_PER_SIGNATURE, SignatureAlgorithm,
51    XMLDSIG_NS, parse_signed_info_with_xpath_budget,
52};
53use super::signature::{encode_ecdsa_signature_as_der, maximum_ecdsa_der_signature_len};
54use super::transforms::{
55    BASE64_TRANSFORM_URI, ENVELOPED_SIGNATURE_URI, MAX_TRANSFORMS_PER_REFERENCE, Transform,
56    TransformExecutionBudget, TransformOptions, XPATH_FILTER2_TRANSFORM_URI, XPATH_TRANSFORM_URI,
57    XPathHereSemantics, XPathSignatureParseBudget, execute_transforms_with_dependency_nodes,
58    execute_transforms_with_options_and_budget, map_c14n_resource_policy_violation,
59    parse_transforms_with_budget, validate_signing_transform_policy,
60};
61use super::types::TransformError;
62use super::uri::{
63    ExternalResourceContext, ExternalResourceMapError, UriReferenceResolver,
64    validate_external_resource_map, validate_signing_reference_request,
65    validate_signing_reference_uri,
66};
67use super::verify::parse_signature_children;
68use crate::document::{DocumentParseSettings, XmlDocument, XmlDocumentError, XmlParseWorkBudget};
69
70/// Result for one computed signing-template reference digest.
71#[derive(Debug, Clone, PartialEq, Eq)]
72#[must_use = "use the computed digest value to fill the corresponding <DigestValue>"]
73pub struct ComputedReferenceDigest {
74    /// Zero-based reference index in `<SignedInfo>` document order.
75    pub index: usize,
76    /// Reference URI used for same-document dereference.
77    pub uri: String,
78    /// Digest algorithm declared by `<DigestMethod>`.
79    pub digest_method: DigestAlgorithm,
80    /// Base64-encoded digest value ready for `<DigestValue>`.
81    pub digest_value: String,
82}
83
84/// Errors returned by the XMLDSig signing digest pass.
85#[derive(Debug, thiserror::Error)]
86pub enum SigningDigestError {
87    /// The selected provider could not compute a reference digest.
88    #[error("cryptographic provider error: {0}")]
89    Provider(#[from] crate::provider::ProviderError),
90
91    /// The compiled signing policy rejected input while processing References.
92    ///
93    /// The lower-level digest APIs return this variant directly. The full
94    /// signing pipeline promotes every policy failure to [`SigningError::Policy`].
95    #[error("signing policy violation: {0}")]
96    Policy(#[from] crate::policy::PolicyViolation),
97
98    /// The input XML document is not well-formed.
99    #[error("XML parse error: {0}")]
100    XmlParse(#[from] crate::xml::dom::ParseError),
101
102    /// The owned document boundary rejected a signing mutation.
103    #[error("XML document error: {0}")]
104    Document(#[from] XmlDocumentError),
105
106    /// Required XMLDSig element is missing.
107    #[error("missing required element: <{element}>")]
108    MissingElement {
109        /// Required element name.
110        element: &'static str,
111    },
112
113    /// XMLDSig template structure is invalid.
114    #[error("invalid signing template: {0}")]
115    InvalidStructure(String),
116
117    /// Digest algorithm URI is not supported.
118    #[error("unsupported digest algorithm: {uri}")]
119    UnsupportedAlgorithm {
120        /// Unrecognized algorithm URI.
121        uri: String,
122    },
123
124    /// Digest algorithm is supported for verification but disabled for signing.
125    #[error("digest algorithm is disabled for signing: {uri}")]
126    SigningAlgorithmDisabled {
127        /// Algorithm URI rejected for new signatures.
128        uri: &'static str,
129    },
130
131    /// URI dereference or transform execution failed.
132    #[error("reference processing error: {0}")]
133    Transform(#[from] TransformError),
134
135    /// Writing computed digest values back into XML failed.
136    #[error("XML mutation error: {0}")]
137    XmlMutation(#[from] XmlMutationError),
138}
139
140impl From<OperationPlanError> for SigningDigestError {
141    fn from(error: OperationPlanError) -> Self {
142        Self::InvalidStructure(error.to_string())
143    }
144}
145
146/// Errors returned by the full XMLDSig signing pipeline.
147#[derive(Debug, thiserror::Error)]
148pub enum SigningError {
149    /// The compiled signing policy rejected input outside the Reference digest stage.
150    #[error("signing policy violation: {0}")]
151    Policy(#[from] crate::policy::PolicyViolation),
152
153    /// Reference digest computation failed.
154    #[error("signing digest pass failed: {0}")]
155    Digest(SigningDigestError),
156
157    /// Parsing the digest-filled `<SignedInfo>` failed.
158    #[error("failed to parse SignedInfo after digest fill: {0}")]
159    ParseSignedInfo(super::parse::ParseError),
160
161    /// SignedInfo canonicalization failed.
162    #[error("SignedInfo canonicalization failed: {0}")]
163    Canonicalization(#[from] crate::c14n::C14nError),
164
165    /// Signing key preparation or signing failed.
166    #[error("signing key error: {0}")]
167    Key(#[from] SigningKeyError),
168
169    /// A signing provider returned bytes that cannot encode this key's signature.
170    #[error("signature output must be {expected} bytes, got {actual}")]
171    InvalidSignatureOutputLength {
172        /// Exact XMLDSig wire length implied by the signing public key.
173        expected: usize,
174        /// Actual provider output length.
175        actual: usize,
176    },
177
178    /// Writing `<SignatureValue>` failed.
179    #[error("XML mutation error: {0}")]
180    XmlMutation(XmlMutationError),
181
182    /// Writing `<KeyInfo>` failed.
183    #[error("KeyInfo writer error: {0}")]
184    KeyInfo(#[from] KeyInfoWriteError),
185
186    /// The owned XML document boundary rejected an identity or mutation.
187    #[error("XML document error: {0}")]
188    Document(#[from] XmlDocumentError),
189
190    /// Signature template generation failed.
191    #[error("signature template error: {0}")]
192    Template(SignatureBuilderError),
193}
194
195impl From<OperationPlanError> for SigningError {
196    fn from(error: OperationPlanError) -> Self {
197        SigningDigestError::from(error).into()
198    }
199}
200
201impl From<SigningDigestError> for SigningError {
202    fn from(error: SigningDigestError) -> Self {
203        match error {
204            SigningDigestError::XmlMutation(XmlMutationError::Policy(error)) => Self::Policy(error),
205            SigningDigestError::Policy(error)
206            | SigningDigestError::Transform(TransformError::Policy(error)) => Self::Policy(error),
207            SigningDigestError::Document(error) => Self::Document(error),
208            error => Self::Digest(error),
209        }
210    }
211}
212
213impl From<super::parse::ParseError> for SigningError {
214    fn from(error: super::parse::ParseError) -> Self {
215        match error {
216            super::parse::ParseError::Policy(error)
217            | super::parse::ParseError::Transform(TransformError::Policy(error)) => {
218                Self::Policy(error)
219            }
220            error => Self::ParseSignedInfo(error),
221        }
222    }
223}
224
225impl From<XmlMutationError> for SigningError {
226    fn from(error: XmlMutationError) -> Self {
227        match error {
228            XmlMutationError::Policy(error) => Self::Policy(error),
229            error => Self::XmlMutation(error),
230        }
231    }
232}
233
234impl From<SignatureBuilderError> for SigningError {
235    fn from(error: SignatureBuilderError) -> Self {
236        match error {
237            SignatureBuilderError::Policy(error) => Self::Policy(error),
238            error => Self::Template(error),
239        }
240    }
241}
242
243/// Errors while parsing or using XMLDSig signing keys.
244#[derive(Debug, thiserror::Error)]
245#[non_exhaustive]
246pub enum SigningKeyError {
247    /// The selected provider cannot execute the requested operation.
248    #[error("cryptographic provider error: {0}")]
249    Provider(#[from] crate::provider::ProviderError),
250
251    /// PEM input could not be parsed.
252    #[error("invalid PEM private key")]
253    InvalidKeyPem,
254
255    /// PEM block was not an unencrypted PKCS#8 private key.
256    #[error("invalid key format: expected PRIVATE KEY PEM, got {label}")]
257    InvalidKeyFormat {
258        /// Actual PEM label.
259        label: String,
260    },
261
262    /// DER bytes could not be decoded for the requested key type.
263    #[error("invalid PKCS#8 private key DER")]
264    InvalidKeyDer,
265
266    /// The signing key cannot produce the requested XMLDSig algorithm.
267    #[error("signing key does not support algorithm: {uri}")]
268    UnsupportedAlgorithm {
269        /// XMLDSig signature algorithm URI.
270        uri: String,
271    },
272
273    /// The private-key signing operation failed.
274    #[error("private-key signing operation failed")]
275    SigningFailed,
276
277    /// Public-key encoding failed for a supported signing key.
278    #[error("failed to encode signing public key as SPKI DER")]
279    PublicKeyEncodingFailed,
280
281    /// Public-key metadata cannot determine the XMLDSig signature framing.
282    #[error("invalid signing public-key metadata")]
283    InvalidPublicKeyInfo,
284}
285
286/// Public key material corresponding to a private XMLDSig signing key.
287#[derive(Debug, Clone, PartialEq, Eq)]
288#[non_exhaustive]
289pub enum SigningPublicKeyInfo {
290    /// RSA public key with DER SubjectPublicKeyInfo and normalized parameters.
291    Rsa {
292        /// DER-encoded SubjectPublicKeyInfo bytes.
293        spki_der: Vec<u8>,
294        /// Unsigned big-endian RSA modulus (`n`), normalized without leading zeroes.
295        modulus: Vec<u8>,
296        /// Unsigned big-endian RSA public exponent (`e`), normalized without leading zeroes.
297        exponent: Vec<u8>,
298    },
299    /// EC public key with DER SubjectPublicKeyInfo and XMLDSig 1.1 KeyValue data.
300    Ec {
301        /// DER-encoded SubjectPublicKeyInfo bytes.
302        spki_der: Vec<u8>,
303        /// Bare named-curve OID, without the XMLDSig `urn:oid:` prefix.
304        curve_oid: &'static str,
305        /// Uncompressed SEC1 point (`0x04 || x || y`).
306        public_key: Vec<u8>,
307    },
308    /// DSA public key and signature component width.
309    Dsa {
310        /// DER-encoded SubjectPublicKeyInfo bytes.
311        spki_der: Vec<u8>,
312        /// Prime modulus P, normalized as unsigned big-endian bytes.
313        p: Vec<u8>,
314        /// Prime divisor Q, normalized as unsigned big-endian bytes.
315        q: Vec<u8>,
316        /// Generator G, normalized as unsigned big-endian bytes.
317        g: Vec<u8>,
318        /// Public value Y, normalized as unsigned big-endian bytes.
319        y: Vec<u8>,
320        /// Prime modulus width used for signing policy.
321        modulus_bits: usize,
322        /// Fixed XMLDSig width of each `r` and `s` component.
323        component_len: usize,
324    },
325    /// Symmetric HMAC key metadata without exposing secret bytes.
326    Hmac {
327        /// Secret length used for signing policy.
328        key_bits: usize,
329    },
330}
331
332impl SigningPublicKeyInfo {
333    /// Return DER-encoded SubjectPublicKeyInfo bytes for this public key.
334    #[must_use]
335    pub fn spki_der(&self) -> Option<&[u8]> {
336        match self {
337            Self::Rsa { spki_der, .. } | Self::Ec { spki_der, .. } | Self::Dsa { spki_der, .. } => {
338                Some(spki_der)
339            }
340            Self::Hmac { .. } => None,
341        }
342    }
343}
344
345/// Validate that a key can produce the requested algorithm under `policy`.
346///
347/// Key registries can use this preflight before selecting a candidate, ensuring
348/// lax ordered searches skip keys that the signing operation would reject.
349pub fn validate_signing_key(
350    key: &dyn SigningKey,
351    algorithm: SignatureAlgorithm,
352    policy: &crate::policy::SigningPolicy,
353) -> Result<(), SigningError> {
354    policy.resources.validate_key_candidates(1)?;
355    policy.check_signature_algorithm(algorithm)?;
356    expected_signature_output_len(key, algorithm, policy, None).map(|_| ())
357}
358
359fn expected_signature_output_len(
360    key: &dyn SigningKey,
361    algorithm: SignatureAlgorithm,
362    policy: &crate::policy::SigningPolicy,
363    hmac_output_length_bits: Option<usize>,
364) -> Result<usize, SigningError> {
365    let public_key = key.public_key_info()?;
366    let expected = match (algorithm, public_key) {
367        (
368            SignatureAlgorithm::RsaSha1
369            | SignatureAlgorithm::RsaSha224
370            | SignatureAlgorithm::RsaSha256
371            | SignatureAlgorithm::RsaSha384
372            | SignatureAlgorithm::RsaSha512,
373            SigningPublicKeyInfo::Rsa {
374                modulus, exponent, ..
375            },
376        ) => policy
377            .rsa_keys
378            .validate_components("signing", &modulus, &exponent)?,
379        (
380            SignatureAlgorithm::EcdsaSha1
381            | SignatureAlgorithm::EcdsaSha224
382            | SignatureAlgorithm::EcdsaSha256
383            | SignatureAlgorithm::EcdsaSha384
384            | SignatureAlgorithm::EcdsaSha512,
385            SigningPublicKeyInfo::Ec { public_key, .. },
386        ) if public_key.first() == Some(&0x04)
387            && public_key.len() > 1
388            && (public_key.len() - 1).is_multiple_of(2) =>
389        {
390            // XMLDSig serializes ECDSA as fixed-width r || s. An uncompressed
391            // SEC1 public point is 0x04 || x || y with the same field width.
392            public_key.len() - 1
393        }
394        (
395            algorithm @ (SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256),
396            SigningPublicKeyInfo::Dsa {
397                modulus_bits,
398                component_len,
399                ..
400            },
401        ) => {
402            policy.dsa_keys.validate_modulus_bits(modulus_bits)?;
403            let required_component_len = algorithm
404                .dsa_component_len()
405                .expect("DSA algorithm matched above");
406            if component_len != required_component_len {
407                return Err(crate::policy::PolicyViolation::InvalidKeyMaterial {
408                    operation: "signing",
409                    key_type: "DSA",
410                    reason: match algorithm {
411                        SignatureAlgorithm::DsaSha1 => "DSA-SHA1 requires a 160-bit q parameter",
412                        SignatureAlgorithm::DsaSha256 => {
413                            "DSA-SHA256 requires a 256-bit q parameter"
414                        }
415                        _ => unreachable!("DSA algorithm matched above"),
416                    },
417                }
418                .into());
419            }
420            component_len.saturating_mul(2)
421        }
422        (
423            SignatureAlgorithm::HmacSha1
424            | SignatureAlgorithm::HmacSha224
425            | SignatureAlgorithm::HmacSha256
426            | SignatureAlgorithm::HmacSha384
427            | SignatureAlgorithm::HmacSha512,
428            SigningPublicKeyInfo::Hmac { key_bits },
429        ) => {
430            policy.hmac.validate_key_bits(key_bits)?;
431            let output_bits = hmac_output_length_bits.unwrap_or(
432                algorithm
433                    .hmac_output_bits()
434                    .ok_or(SigningKeyError::InvalidPublicKeyInfo)?,
435            );
436            policy.hmac.validate_output(algorithm, output_bits)?;
437            output_bits / 8
438        }
439        (
440            SignatureAlgorithm::RsaSha1
441            | SignatureAlgorithm::RsaSha224
442            | SignatureAlgorithm::RsaSha256
443            | SignatureAlgorithm::RsaSha384
444            | SignatureAlgorithm::RsaSha512,
445            SigningPublicKeyInfo::Ec { .. }
446            | SigningPublicKeyInfo::Dsa { .. }
447            | SigningPublicKeyInfo::Hmac { .. },
448        )
449        | (
450            SignatureAlgorithm::EcdsaSha1
451            | SignatureAlgorithm::EcdsaSha224
452            | SignatureAlgorithm::EcdsaSha256
453            | SignatureAlgorithm::EcdsaSha384
454            | SignatureAlgorithm::EcdsaSha512,
455            SigningPublicKeyInfo::Rsa { .. }
456            | SigningPublicKeyInfo::Dsa { .. }
457            | SigningPublicKeyInfo::Hmac { .. },
458        ) => {
459            return Err(SigningKeyError::UnsupportedAlgorithm {
460                uri: algorithm.uri().to_owned(),
461            }
462            .into());
463        }
464        _ => return Err(SigningKeyError::InvalidPublicKeyInfo.into()),
465    };
466    Ok(expected)
467}
468
469fn validate_signature_output(expected: usize, signature: &[u8]) -> Result<(), SigningError> {
470    if signature.len() != expected {
471        return Err(SigningError::InvalidSignatureOutputLength {
472            expected,
473            actual: signature.len(),
474        });
475    }
476    Ok(())
477}
478
479/// Private key abstraction used by [`SignContext`].
480pub trait SigningKey {
481    /// Sign canonicalized `<SignedInfo>` bytes for the declared XMLDSig method.
482    fn sign(
483        &self,
484        algorithm: SignatureAlgorithm,
485        canonical_signed_info: &[u8],
486    ) -> Result<Vec<u8>, SigningKeyError>;
487
488    /// Sign while sourcing any primitive randomness from the selected provider.
489    ///
490    /// Deterministic or externally managed keys can rely on this default. Keys
491    /// whose primitive uses randomness, including RSA blinding, must override it.
492    fn sign_with_provider(
493        &self,
494        provider: &dyn crate::provider::CryptoProvider,
495        algorithm: SignatureAlgorithm,
496        canonical_signed_info: &[u8],
497    ) -> Result<Vec<u8>, SigningKeyError> {
498        let _ = provider;
499        self.sign(algorithm, canonical_signed_info)
500    }
501
502    /// Return structured public key material corresponding to this signing key.
503    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError>;
504}
505
506/// Writes signing key metadata into a template `<KeyInfo>` element.
507pub trait KeyInfoWriter {
508    /// Return XML child content for the direct `<Signature>/<KeyInfo>` element.
509    fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError>;
510
511    /// Write key metadata through the cryptographic provider selected for the operation.
512    ///
513    /// Writers that do not perform cryptographic operations can rely on this
514    /// default. Digest- or signature-producing writers must override it.
515    fn write_key_info_with_provider(
516        &self,
517        signing_key: &dyn SigningKey,
518        provider: &dyn crate::provider::CryptoProvider,
519    ) -> Result<String, KeyInfoWriteError> {
520        let _ = provider;
521        self.write_key_info(signing_key)
522    }
523}
524
525/// Errors while preparing XMLDSig signing `<KeyInfo>` output.
526#[derive(Debug, thiserror::Error)]
527#[non_exhaustive]
528pub enum KeyInfoWriteError {
529    /// The selected provider could not produce cryptographic key metadata.
530    #[error("cryptographic provider error: {0}")]
531    Provider(#[from] crate::provider::ProviderError),
532
533    /// PEM input could not be parsed.
534    #[error("invalid PEM certificate")]
535    InvalidCertificatePem,
536
537    /// PEM block was not an X.509 certificate.
538    #[error("invalid certificate format: expected CERTIFICATE PEM, got {label}")]
539    InvalidCertificateFormat {
540        /// Actual PEM label.
541        label: String,
542    },
543
544    /// DER bytes could not be decoded as one complete X.509 certificate.
545    #[error("invalid X.509 certificate DER")]
546    InvalidCertificateDer,
547
548    /// A certificate-backed KeyInfo writer requires at least one certificate.
549    #[error("X.509 certificate chain must not be empty")]
550    EmptyCertificateChain,
551
552    /// The signing key could not expose public-key material for validation.
553    #[error("signing key public-key extraction failed: {0}")]
554    SigningKey(#[from] SigningKeyError),
555
556    /// Symmetric signing keys cannot expose an asymmetric public key value.
557    #[error("signing key has no DER-encodable public key")]
558    MissingPublicKey,
559
560    /// The selected writer cannot represent this public-key family.
561    #[error("signing key cannot be represented as XMLDSig KeyValue")]
562    UnsupportedKeyValue,
563
564    /// The configured certificate does not contain the signing key's public key.
565    #[error("X.509 certificate public key does not match signing key")]
566    CertificateKeyMismatch,
567}
568
569/// Writes the signing key's SPKI as XMLDSig 1.1 `DEREncodedKeyValue`.
570#[derive(Debug, Clone, Copy, Default)]
571pub struct DerEncodedKeyValueInfoWriter;
572
573impl KeyInfoWriter for DerEncodedKeyValueInfoWriter {
574    fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
575        let public_key = signing_key.public_key_info()?;
576        let spki_der = public_key
577            .spki_der()
578            .ok_or(KeyInfoWriteError::MissingPublicKey)?;
579        let encoded = base64::engine::general_purpose::STANDARD.encode(spki_der);
580        Ok(format!(
581            "<dsig11:DEREncodedKeyValue xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\">{encoded}</dsig11:DEREncodedKeyValue>"
582        ))
583    }
584}
585
586/// Writes RSA, DSA, or XMLDSig 1.1 EC public parameters as `KeyValue`.
587#[derive(Debug, Clone, Copy, Default)]
588pub struct KeyValueInfoWriter;
589
590impl KeyInfoWriter for KeyValueInfoWriter {
591    fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
592        let encode = |bytes: &[u8]| base64::engine::general_purpose::STANDARD.encode(bytes);
593        match signing_key.public_key_info()? {
594            SigningPublicKeyInfo::Rsa {
595                modulus, exponent, ..
596            } => Ok(format!(
597                "<ds:KeyValue xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:RSAKeyValue><ds:Modulus>{}</ds:Modulus><ds:Exponent>{}</ds:Exponent></ds:RSAKeyValue></ds:KeyValue>",
598                encode(&modulus),
599                encode(&exponent)
600            )),
601            SigningPublicKeyInfo::Ec {
602                curve_oid,
603                public_key,
604                ..
605            } => Ok(format!(
606                "<ds:KeyValue xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><dsig11:ECKeyValue xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\"><dsig11:NamedCurve URI=\"urn:oid:{curve_oid}\"/><dsig11:PublicKey>{}</dsig11:PublicKey></dsig11:ECKeyValue></ds:KeyValue>",
607                encode(&public_key)
608            )),
609            SigningPublicKeyInfo::Dsa { p, q, g, y, .. } => Ok(format!(
610                "<ds:KeyValue xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:DSAKeyValue><ds:P>{}</ds:P><ds:Q>{}</ds:Q><ds:G>{}</ds:G><ds:Y>{}</ds:Y></ds:DSAKeyValue></ds:KeyValue>",
611                encode(&p),
612                encode(&q),
613                encode(&g),
614                encode(&y)
615            )),
616            SigningPublicKeyInfo::Hmac { .. } => Err(KeyInfoWriteError::UnsupportedKeyValue),
617        }
618    }
619}
620
621/// `<KeyInfo>` writer that embeds an ordered DER X.509 certificate chain.
622pub struct X509CertificateKeyInfoWriter {
623    certificates_der: Vec<Vec<u8>>,
624}
625
626impl X509CertificateKeyInfoWriter {
627    /// Parse a PEM `CERTIFICATE` block for XMLDSig `<X509Certificate>` output.
628    pub fn from_pem(certificate_pem: &str) -> Result<Self, KeyInfoWriteError> {
629        Self::from_pem_chain([certificate_pem])
630    }
631
632    /// Parse a leaf-first sequence of PEM `CERTIFICATE` blocks for `<X509Data>`.
633    ///
634    /// The first certificate must identify the signing key. Remaining issuer
635    /// certificates are emitted in caller order; this writer does not build or
636    /// validate issuer relationships because trust remains caller-owned.
637    pub fn from_pem_chain<I, S>(certificate_pems: I) -> Result<Self, KeyInfoWriteError>
638    where
639        I: IntoIterator<Item = S>,
640        S: AsRef<str>,
641    {
642        let mut certificates_der = Vec::new();
643        for certificate_pem in certificate_pems {
644            certificates_der.push(parse_certificate_pem(certificate_pem.as_ref())?);
645        }
646        Self::from_der_chain(certificates_der)
647    }
648
649    /// Validate and store DER certificate bytes for XMLDSig `<X509Certificate>` output.
650    pub fn from_der(certificate_der: &[u8]) -> Result<Self, KeyInfoWriteError> {
651        Self::from_der_chain([certificate_der])
652    }
653
654    /// Validate and store a leaf-first DER certificate chain for `<X509Data>`.
655    ///
656    /// The first certificate must identify the signing key. Remaining issuer
657    /// certificates are emitted in caller order; this writer does not build or
658    /// validate issuer relationships because trust remains caller-owned.
659    pub fn from_der_chain<I, B>(certificates_der: I) -> Result<Self, KeyInfoWriteError>
660    where
661        I: IntoIterator<Item = B>,
662        B: AsRef<[u8]>,
663    {
664        let certificates_der = certificates_der
665            .into_iter()
666            .map(|certificate_der| {
667                let certificate_der = certificate_der.as_ref();
668                let (rest, _) =
669                    x509_parser::certificate::X509Certificate::from_der(certificate_der)
670                        .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
671                if !rest.is_empty() {
672                    return Err(KeyInfoWriteError::InvalidCertificateDer);
673                }
674                Ok(certificate_der.to_vec())
675            })
676            .collect::<Result<Vec<_>, _>>()?;
677        if certificates_der.is_empty() {
678            return Err(KeyInfoWriteError::EmptyCertificateChain);
679        }
680        Ok(Self { certificates_der })
681    }
682}
683
684fn parse_certificate_pem(certificate_pem: &str) -> Result<Vec<u8>, KeyInfoWriteError> {
685    let (rest, pem) = x509_parser::pem::parse_x509_pem(certificate_pem.as_bytes())
686        .map_err(|_| KeyInfoWriteError::InvalidCertificatePem)?;
687    if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
688        return Err(KeyInfoWriteError::InvalidCertificatePem);
689    }
690    if pem.label != "CERTIFICATE" {
691        return Err(KeyInfoWriteError::InvalidCertificateFormat { label: pem.label });
692    }
693    Ok(pem.contents)
694}
695
696impl KeyInfoWriter for X509CertificateKeyInfoWriter {
697    fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
698        let leaf_der = &self.certificates_der[0];
699        let (rest, certificate) = x509_parser::certificate::X509Certificate::from_der(leaf_der)
700            .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
701        if !rest.is_empty() {
702            return Err(KeyInfoWriteError::InvalidCertificateDer);
703        }
704        let signing_public_key = signing_key.public_key_info()?;
705        if signing_public_key.spki_der() != Some(certificate.public_key().raw) {
706            return Err(KeyInfoWriteError::CertificateKeyMismatch);
707        }
708
709        let mut xml = format!("<X509Data xmlns=\"{XMLDSIG_NS}\">");
710        for certificate_der in &self.certificates_der {
711            let certificate_b64 = base64::engine::general_purpose::STANDARD.encode(certificate_der);
712            xml.push_str("<X509Certificate>");
713            xml.push_str(&certificate_b64);
714            xml.push_str("</X509Certificate>");
715        }
716        xml.push_str("</X509Data>");
717        Ok(xml)
718    }
719}
720
721/// Writes an XMLDSig 1.1 `X509Digest` selector for the signing certificate.
722pub struct X509DigestKeyInfoWriter {
723    certificate_der: Vec<u8>,
724    certificate_spki_der: Vec<u8>,
725    digest_algorithm: DigestAlgorithm,
726}
727
728impl X509DigestKeyInfoWriter {
729    /// Validate and retain a DER certificate and selector digest algorithm.
730    pub fn from_der(
731        certificate_der: &[u8],
732        digest_algorithm: DigestAlgorithm,
733    ) -> Result<Self, KeyInfoWriteError> {
734        let (rest, certificate) =
735            x509_parser::certificate::X509Certificate::from_der(certificate_der)
736                .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
737        if !rest.is_empty() {
738            return Err(KeyInfoWriteError::InvalidCertificateDer);
739        }
740        Ok(Self {
741            certificate_der: certificate_der.to_vec(),
742            certificate_spki_der: certificate.public_key().raw.to_vec(),
743            digest_algorithm,
744        })
745    }
746
747    /// Parse a PEM certificate and retain its selector digest algorithm.
748    pub fn from_pem(
749        certificate_pem: &str,
750        digest_algorithm: DigestAlgorithm,
751    ) -> Result<Self, KeyInfoWriteError> {
752        Self::from_der(&parse_certificate_pem(certificate_pem)?, digest_algorithm)
753    }
754}
755
756impl KeyInfoWriter for X509DigestKeyInfoWriter {
757    fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
758        self.write_key_info_with_provider(signing_key, crate::provider::default_provider())
759    }
760
761    fn write_key_info_with_provider(
762        &self,
763        signing_key: &dyn SigningKey,
764        provider: &dyn crate::provider::CryptoProvider,
765    ) -> Result<String, KeyInfoWriteError> {
766        if signing_key.public_key_info()?.spki_der() != Some(self.certificate_spki_der.as_slice()) {
767            return Err(KeyInfoWriteError::CertificateKeyMismatch);
768        }
769        let digest = super::compute_digest_with_provider(
770            provider,
771            self.digest_algorithm,
772            &self.certificate_der,
773        )?;
774        let encoded = base64::engine::general_purpose::STANDARD.encode(digest);
775        Ok(format!(
776            "<ds:X509Data xmlns:ds=\"{XMLDSIG_NS}\"><dsig11:X509Digest xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\" Algorithm=\"{}\">{encoded}</dsig11:X509Digest></ds:X509Data>",
777            self.digest_algorithm.uri()
778        ))
779    }
780}
781
782/// RSA PKCS#1 v1.5 private key for XMLDSig signing.
783pub struct RsaSigningKey {
784    key: RsaPrivateKey,
785}
786
787impl RsaSigningKey {
788    /// Parse an unencrypted PKCS#8 `PRIVATE KEY` PEM block.
789    pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
790        let private_key_der = parse_private_key_pem(private_key_pem)?;
791        Self::from_pkcs8_der(&private_key_der)
792    }
793
794    /// Parse unencrypted PKCS#8 private key DER.
795    pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
796        let key = RsaPrivateKey::from_pkcs8_der(private_key_der)
797            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
798        Ok(Self { key })
799    }
800
801    /// Decrypt and parse a password-protected PKCS#8 `ENCRYPTED PRIVATE KEY` PEM block.
802    pub fn from_pkcs8_encrypted_pem(
803        private_key_pem: &str,
804        password: impl AsRef<[u8]>,
805    ) -> Result<Self, SigningKeyError> {
806        let key = RsaPrivateKey::from_pkcs8_encrypted_pem(private_key_pem, password)
807            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
808        Ok(Self { key })
809    }
810
811    /// Decrypt and parse password-protected PKCS#8 DER.
812    pub fn from_pkcs8_encrypted_der(
813        private_key_der: &[u8],
814        password: impl AsRef<[u8]>,
815    ) -> Result<Self, SigningKeyError> {
816        let key = RsaPrivateKey::from_pkcs8_encrypted_der(private_key_der, password)
817            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
818        Ok(Self { key })
819    }
820}
821
822impl SigningKey for RsaSigningKey {
823    fn sign(
824        &self,
825        algorithm: SignatureAlgorithm,
826        canonical_signed_info: &[u8],
827    ) -> Result<Vec<u8>, SigningKeyError> {
828        self.sign_with_provider(
829            crate::provider::default_provider(),
830            algorithm,
831            canonical_signed_info,
832        )
833    }
834
835    fn sign_with_provider(
836        &self,
837        provider: &dyn crate::provider::CryptoProvider,
838        algorithm: SignatureAlgorithm,
839        canonical_signed_info: &[u8],
840    ) -> Result<Vec<u8>, SigningKeyError> {
841        match algorithm {
842            SignatureAlgorithm::RsaSha1 => sign_rsa_pkcs1v15_with_rng(
843                provider,
844                RsaPkcs1v15SigningKey::<Sha1>::new(self.key.clone()),
845                canonical_signed_info,
846            ),
847            SignatureAlgorithm::RsaSha224 => sign_rsa_pkcs1v15_with_rng(
848                provider,
849                RsaPkcs1v15SigningKey::<Sha224>::new(self.key.clone()),
850                canonical_signed_info,
851            ),
852            SignatureAlgorithm::RsaSha256 => sign_rsa_pkcs1v15_with_rng(
853                provider,
854                RsaPkcs1v15SigningKey::<Sha256>::new(self.key.clone()),
855                canonical_signed_info,
856            ),
857            SignatureAlgorithm::RsaSha384 => sign_rsa_pkcs1v15_with_rng(
858                provider,
859                RsaPkcs1v15SigningKey::<Sha384>::new(self.key.clone()),
860                canonical_signed_info,
861            ),
862            SignatureAlgorithm::RsaSha512 => sign_rsa_pkcs1v15_with_rng(
863                provider,
864                RsaPkcs1v15SigningKey::<Sha512>::new(self.key.clone()),
865                canonical_signed_info,
866            ),
867            _ => Err(SigningKeyError::UnsupportedAlgorithm {
868                uri: algorithm.uri().to_string(),
869            }),
870        }
871    }
872
873    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
874        let public_key = self.key.to_public_key();
875        let spki_der = public_key
876            .to_public_key_der()
877            .map(|doc| doc.as_bytes().to_vec())
878            .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
879        Ok(SigningPublicKeyInfo::Rsa {
880            spki_der,
881            modulus: public_key.n().to_be_bytes_trimmed_vartime().into_vec(),
882            exponent: public_key.e().to_be_bytes_trimmed_vartime().into_vec(),
883        })
884    }
885}
886
887/// Symmetric key for XMLDSig HMAC signing.
888///
889/// Owned secret bytes are zeroized when the key is dropped.
890pub struct HmacSigningKey {
891    secret: Zeroizing<Vec<u8>>,
892}
893
894impl HmacSigningKey {
895    /// Construct a signing key from non-empty caller-owned secret bytes.
896    pub fn new(secret: impl Into<Vec<u8>>) -> Result<Self, SigningKeyError> {
897        let secret = secret.into();
898        if secret.is_empty() {
899            return Err(SigningKeyError::InvalidKeyDer);
900        }
901        Ok(Self {
902            secret: Zeroizing::new(secret),
903        })
904    }
905}
906
907impl SigningKey for HmacSigningKey {
908    fn sign(
909        &self,
910        algorithm: SignatureAlgorithm,
911        canonical_signed_info: &[u8],
912    ) -> Result<Vec<u8>, SigningKeyError> {
913        macro_rules! sign_hmac {
914            ($digest:ty) => {{
915                let mut mac = hmac::Hmac::<$digest>::new_from_slice(&self.secret)
916                    .map_err(|_| SigningKeyError::InvalidKeyDer)?;
917                mac.update(canonical_signed_info);
918                mac.finalize().into_bytes().to_vec()
919            }};
920        }
921        Ok(match algorithm {
922            SignatureAlgorithm::HmacSha1 => sign_hmac!(sha1::Sha1),
923            SignatureAlgorithm::HmacSha224 => sign_hmac!(sha2::Sha224),
924            SignatureAlgorithm::HmacSha256 => sign_hmac!(sha2::Sha256),
925            SignatureAlgorithm::HmacSha384 => sign_hmac!(sha2::Sha384),
926            SignatureAlgorithm::HmacSha512 => sign_hmac!(sha2::Sha512),
927            _ => {
928                return Err(SigningKeyError::UnsupportedAlgorithm {
929                    uri: algorithm.uri().to_owned(),
930                });
931            }
932        })
933    }
934
935    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
936        Ok(SigningPublicKeyInfo::Hmac {
937            key_bits: self.secret.len().saturating_mul(8),
938        })
939    }
940}
941
942/// DSA private key for XMLDSig 1.1 signing.
943pub struct DsaSigningKey {
944    key: dsa::SigningKey,
945}
946
947impl DsaSigningKey {
948    /// Parse an unencrypted PKCS#8 `PRIVATE KEY` PEM block.
949    pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
950        let private_key_der = parse_private_key_pem(private_key_pem)?;
951        Self::from_pkcs8_der(&private_key_der)
952    }
953
954    /// Parse unencrypted PKCS#8 private key DER.
955    pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
956        let key = dsa::SigningKey::from_pkcs8_der(private_key_der)
957            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
958        Ok(Self { key })
959    }
960
961    /// Decrypt and parse a password-protected PKCS#8 `ENCRYPTED PRIVATE KEY` PEM block.
962    pub fn from_pkcs8_encrypted_pem(
963        private_key_pem: &str,
964        password: impl AsRef<[u8]>,
965    ) -> Result<Self, SigningKeyError> {
966        let key = dsa::SigningKey::from_pkcs8_encrypted_pem(private_key_pem, password)
967            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
968        Ok(Self { key })
969    }
970
971    /// Decrypt and parse password-protected PKCS#8 DER.
972    pub fn from_pkcs8_encrypted_der(
973        private_key_der: &[u8],
974        password: impl AsRef<[u8]>,
975    ) -> Result<Self, SigningKeyError> {
976        let key = dsa::SigningKey::from_pkcs8_encrypted_der(private_key_der, password)
977            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
978        Ok(Self { key })
979    }
980}
981
982impl SigningKey for DsaSigningKey {
983    fn sign(
984        &self,
985        algorithm: SignatureAlgorithm,
986        canonical_signed_info: &[u8],
987    ) -> Result<Vec<u8>, SigningKeyError> {
988        self.sign_with_provider(
989            crate::provider::default_provider(),
990            algorithm,
991            canonical_signed_info,
992        )
993    }
994
995    fn sign_with_provider(
996        &self,
997        provider: &dyn crate::provider::CryptoProvider,
998        algorithm: SignatureAlgorithm,
999        canonical_signed_info: &[u8],
1000    ) -> Result<Vec<u8>, SigningKeyError> {
1001        let digest_algorithm = match algorithm {
1002            SignatureAlgorithm::DsaSha1 => DigestAlgorithm::Sha1,
1003            SignatureAlgorithm::DsaSha256 => DigestAlgorithm::Sha256,
1004            _ => {
1005                return Err(SigningKeyError::UnsupportedAlgorithm {
1006                    uri: algorithm.uri().to_owned(),
1007                });
1008            }
1009        };
1010        let component_len =
1011            usize::try_from(self.key.verifying_key().components().q().bits_vartime())
1012                .map_err(|_| SigningKeyError::InvalidPublicKeyInfo)?
1013                .div_ceil(8);
1014        if algorithm.dsa_component_len() != Some(component_len) {
1015            return Err(SigningKeyError::InvalidPublicKeyInfo);
1016        }
1017        let digest =
1018            super::compute_digest_with_provider(provider, digest_algorithm, canonical_signed_info)?;
1019        let mut rng = crate::provider::ProviderRng(provider);
1020        let signature: dsa::Signature = self
1021            .key
1022            .sign_prehash_with_rng(&mut rng, &digest)
1023            .map_err(|_| SigningKeyError::SigningFailed)?;
1024        let mut output = Vec::with_capacity(component_len.saturating_mul(2));
1025        for component in [signature.r(), signature.s()] {
1026            let bytes = component.to_be_bytes_trimmed_vartime();
1027            if bytes.len() > component_len {
1028                return Err(SigningKeyError::SigningFailed);
1029            }
1030            output.resize(output.len() + component_len - bytes.len(), 0);
1031            output.extend_from_slice(bytes.as_ref());
1032        }
1033        Ok(output)
1034    }
1035
1036    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1037        let verifying_key = self.key.verifying_key();
1038        let spki_der = verifying_key
1039            .to_public_key_der()
1040            .map(|doc| doc.as_bytes().to_vec())
1041            .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
1042        let modulus_bits = usize::try_from(verifying_key.components().p().bits_vartime())
1043            .map_err(|_| SigningKeyError::InvalidPublicKeyInfo)?;
1044        let component_len = usize::try_from(verifying_key.components().q().bits_vartime())
1045            .map_err(|_| SigningKeyError::InvalidPublicKeyInfo)?
1046            .div_ceil(8);
1047        let components = verifying_key.components();
1048        Ok(SigningPublicKeyInfo::Dsa {
1049            spki_der,
1050            p: components.p().to_be_bytes_trimmed_vartime().to_vec(),
1051            q: components.q().to_be_bytes_trimmed_vartime().to_vec(),
1052            g: components.g().to_be_bytes_trimmed_vartime().to_vec(),
1053            y: verifying_key.y().to_be_bytes_trimmed_vartime().to_vec(),
1054            modulus_bits,
1055            component_len,
1056        })
1057    }
1058}
1059
1060fn sign_rsa_pkcs1v15_with_rng(
1061    provider: &dyn crate::provider::CryptoProvider,
1062    key: impl RandomizedSigner<RsaPkcs1v15Signature>,
1063    canonical_signed_info: &[u8],
1064) -> Result<Vec<u8>, SigningKeyError> {
1065    let mut rng = crate::provider::ProviderRng(provider);
1066    let signature = key
1067        .try_sign_with_rng(&mut rng, canonical_signed_info)
1068        .map_err(|_| SigningKeyError::SigningFailed)?;
1069    Ok(signature.to_vec())
1070}
1071
1072fn ecdsa_digest_algorithm(
1073    algorithm: SignatureAlgorithm,
1074) -> Result<DigestAlgorithm, SigningKeyError> {
1075    match algorithm {
1076        SignatureAlgorithm::EcdsaSha1 => Ok(DigestAlgorithm::Sha1),
1077        SignatureAlgorithm::EcdsaSha224 => Ok(DigestAlgorithm::Sha224),
1078        SignatureAlgorithm::EcdsaSha256 => Ok(DigestAlgorithm::Sha256),
1079        SignatureAlgorithm::EcdsaSha384 => Ok(DigestAlgorithm::Sha384),
1080        SignatureAlgorithm::EcdsaSha512 => Ok(DigestAlgorithm::Sha512),
1081        _ => Err(SigningKeyError::UnsupportedAlgorithm {
1082            uri: algorithm.uri().to_owned(),
1083        }),
1084    }
1085}
1086
1087fn sign_ecdsa_with_provider<S, K>(
1088    key: &K,
1089    provider: &dyn crate::provider::CryptoProvider,
1090    algorithm: SignatureAlgorithm,
1091    canonical_signed_info: &[u8],
1092) -> Result<Vec<u8>, SigningKeyError>
1093where
1094    K: PrehashSigner<S>,
1095    S: SignatureEncoding,
1096{
1097    let digest_algorithm = ecdsa_digest_algorithm(algorithm)?;
1098    let prehash =
1099        super::compute_digest_with_provider(provider, digest_algorithm, canonical_signed_info)?;
1100    let signature = key
1101        .sign_prehash(&prehash)
1102        .map_err(|_| SigningKeyError::SigningFailed)?;
1103    Ok(signature.to_vec())
1104}
1105
1106trait EcdsaPublicKeyEncoding {
1107    fn spki_der(&self) -> Result<Vec<u8>, SigningKeyError>;
1108    fn uncompressed_sec1(&self) -> Vec<u8>;
1109}
1110
1111macro_rules! impl_ecdsa_public_key_encoding {
1112    ($key:ty) => {
1113        impl EcdsaPublicKeyEncoding for $key {
1114            fn spki_der(&self) -> Result<Vec<u8>, SigningKeyError> {
1115                self.to_public_key_der()
1116                    .map(|document| document.as_bytes().to_vec())
1117                    .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)
1118            }
1119
1120            fn uncompressed_sec1(&self) -> Vec<u8> {
1121                self.to_sec1_point(false).as_bytes().to_vec()
1122            }
1123        }
1124    };
1125}
1126
1127impl_ecdsa_public_key_encoding!(P256VerifyingKey);
1128impl_ecdsa_public_key_encoding!(P384VerifyingKey);
1129impl_ecdsa_public_key_encoding!(P521VerifyingKey);
1130
1131fn ecdsa_public_key_info(
1132    key: &impl EcdsaPublicKeyEncoding,
1133    curve_oid: &'static str,
1134) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1135    Ok(SigningPublicKeyInfo::Ec {
1136        spki_der: key.spki_der()?,
1137        curve_oid,
1138        public_key: key.uncompressed_sec1(),
1139    })
1140}
1141
1142/// ECDSA P-256 private key for XMLDSig signing.
1143pub struct EcdsaP256SigningKey {
1144    key: P256SigningKey,
1145}
1146
1147impl EcdsaP256SigningKey {
1148    /// Parse an unencrypted SEC1 `EC PRIVATE KEY` PEM block.
1149    pub fn from_sec1_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1150        let key = p256::SecretKey::from_sec1_pem(private_key_pem)
1151            .map(P256SigningKey::from)
1152            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1153        Ok(Self { key })
1154    }
1155
1156    /// Parse unencrypted SEC1 `ECPrivateKey` DER.
1157    pub fn from_sec1_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1158        let key = p256::SecretKey::from_sec1_der(private_key_der)
1159            .map(P256SigningKey::from)
1160            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1161        Ok(Self { key })
1162    }
1163
1164    /// Parse an unencrypted PKCS#8 `PRIVATE KEY` PEM block.
1165    pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1166        let private_key_der = parse_private_key_pem(private_key_pem)?;
1167        Self::from_pkcs8_der(&private_key_der)
1168    }
1169
1170    /// Parse unencrypted PKCS#8 private key DER.
1171    pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1172        let key = P256SigningKey::from_pkcs8_der(private_key_der)
1173            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1174        Ok(Self { key })
1175    }
1176
1177    /// Decrypt and parse a password-protected PKCS#8 `ENCRYPTED PRIVATE KEY` PEM block.
1178    pub fn from_pkcs8_encrypted_pem(
1179        private_key_pem: &str,
1180        password: impl AsRef<[u8]>,
1181    ) -> Result<Self, SigningKeyError> {
1182        let key = P256SigningKey::from_pkcs8_encrypted_pem(private_key_pem, password)
1183            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1184        Ok(Self { key })
1185    }
1186
1187    /// Decrypt and parse password-protected PKCS#8 DER.
1188    pub fn from_pkcs8_encrypted_der(
1189        private_key_der: &[u8],
1190        password: impl AsRef<[u8]>,
1191    ) -> Result<Self, SigningKeyError> {
1192        let key = P256SigningKey::from_pkcs8_encrypted_der(private_key_der, password)
1193            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1194        Ok(Self { key })
1195    }
1196}
1197
1198impl SigningKey for EcdsaP256SigningKey {
1199    fn sign(
1200        &self,
1201        algorithm: SignatureAlgorithm,
1202        canonical_signed_info: &[u8],
1203    ) -> Result<Vec<u8>, SigningKeyError> {
1204        self.sign_with_provider(
1205            crate::provider::default_provider(),
1206            algorithm,
1207            canonical_signed_info,
1208        )
1209    }
1210
1211    fn sign_with_provider(
1212        &self,
1213        provider: &dyn crate::provider::CryptoProvider,
1214        algorithm: SignatureAlgorithm,
1215        canonical_signed_info: &[u8],
1216    ) -> Result<Vec<u8>, SigningKeyError> {
1217        sign_ecdsa_with_provider::<P256Signature, _>(
1218            &self.key,
1219            provider,
1220            algorithm,
1221            canonical_signed_info,
1222        )
1223    }
1224
1225    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1226        ecdsa_public_key_info(self.key.verifying_key(), EC_P256_OID)
1227    }
1228}
1229
1230/// ECDSA P-384 private key for XMLDSig signing.
1231pub struct EcdsaP384SigningKey {
1232    key: P384SigningKey,
1233}
1234
1235impl EcdsaP384SigningKey {
1236    /// Parse an unencrypted SEC1 `EC PRIVATE KEY` PEM block.
1237    pub fn from_sec1_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1238        let key = p384::SecretKey::from_sec1_pem(private_key_pem)
1239            .map(P384SigningKey::from)
1240            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1241        Ok(Self { key })
1242    }
1243
1244    /// Parse unencrypted SEC1 `ECPrivateKey` DER.
1245    pub fn from_sec1_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1246        let key = p384::SecretKey::from_sec1_der(private_key_der)
1247            .map(P384SigningKey::from)
1248            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1249        Ok(Self { key })
1250    }
1251
1252    /// Parse an unencrypted PKCS#8 `PRIVATE KEY` PEM block.
1253    pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1254        let private_key_der = parse_private_key_pem(private_key_pem)?;
1255        Self::from_pkcs8_der(&private_key_der)
1256    }
1257
1258    /// Parse unencrypted PKCS#8 private key DER.
1259    pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1260        let key = P384SigningKey::from_pkcs8_der(private_key_der)
1261            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1262        Ok(Self { key })
1263    }
1264
1265    /// Decrypt and parse a password-protected PKCS#8 `ENCRYPTED PRIVATE KEY` PEM block.
1266    pub fn from_pkcs8_encrypted_pem(
1267        private_key_pem: &str,
1268        password: impl AsRef<[u8]>,
1269    ) -> Result<Self, SigningKeyError> {
1270        let key = P384SigningKey::from_pkcs8_encrypted_pem(private_key_pem, password)
1271            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1272        Ok(Self { key })
1273    }
1274
1275    /// Decrypt and parse password-protected PKCS#8 DER.
1276    pub fn from_pkcs8_encrypted_der(
1277        private_key_der: &[u8],
1278        password: impl AsRef<[u8]>,
1279    ) -> Result<Self, SigningKeyError> {
1280        let key = P384SigningKey::from_pkcs8_encrypted_der(private_key_der, password)
1281            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1282        Ok(Self { key })
1283    }
1284}
1285
1286impl SigningKey for EcdsaP384SigningKey {
1287    fn sign(
1288        &self,
1289        algorithm: SignatureAlgorithm,
1290        canonical_signed_info: &[u8],
1291    ) -> Result<Vec<u8>, SigningKeyError> {
1292        self.sign_with_provider(
1293            crate::provider::default_provider(),
1294            algorithm,
1295            canonical_signed_info,
1296        )
1297    }
1298
1299    fn sign_with_provider(
1300        &self,
1301        provider: &dyn crate::provider::CryptoProvider,
1302        algorithm: SignatureAlgorithm,
1303        canonical_signed_info: &[u8],
1304    ) -> Result<Vec<u8>, SigningKeyError> {
1305        sign_ecdsa_with_provider::<P384Signature, _>(
1306            &self.key,
1307            provider,
1308            algorithm,
1309            canonical_signed_info,
1310        )
1311    }
1312
1313    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1314        ecdsa_public_key_info(self.key.verifying_key(), EC_P384_OID)
1315    }
1316}
1317
1318/// ECDSA P-521 private key for XMLDSig signing.
1319pub struct EcdsaP521SigningKey {
1320    key: P521SigningKey,
1321}
1322
1323impl EcdsaP521SigningKey {
1324    /// Parse an unencrypted SEC1 `EC PRIVATE KEY` PEM block.
1325    pub fn from_sec1_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1326        let key = p521::SecretKey::from_sec1_pem(private_key_pem)
1327            .map(P521SigningKey::from)
1328            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1329        Ok(Self { key })
1330    }
1331
1332    /// Parse unencrypted SEC1 `ECPrivateKey` DER.
1333    pub fn from_sec1_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1334        let key = p521::SecretKey::from_sec1_der(private_key_der)
1335            .map(P521SigningKey::from)
1336            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1337        Ok(Self { key })
1338    }
1339
1340    /// Parse an unencrypted PKCS#8 `PRIVATE KEY` PEM block.
1341    pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1342        let private_key_der = parse_private_key_pem(private_key_pem)?;
1343        Self::from_pkcs8_der(&private_key_der)
1344    }
1345
1346    /// Parse unencrypted PKCS#8 private key DER.
1347    pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1348        let key = P521SigningKey::from_pkcs8_der(private_key_der)
1349            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1350        Ok(Self { key })
1351    }
1352
1353    /// Decrypt and parse a password-protected PKCS#8 `ENCRYPTED PRIVATE KEY` PEM block.
1354    pub fn from_pkcs8_encrypted_pem(
1355        private_key_pem: &str,
1356        password: impl AsRef<[u8]>,
1357    ) -> Result<Self, SigningKeyError> {
1358        let key = P521SigningKey::from_pkcs8_encrypted_pem(private_key_pem, password)
1359            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1360        Ok(Self { key })
1361    }
1362
1363    /// Decrypt and parse password-protected PKCS#8 DER.
1364    pub fn from_pkcs8_encrypted_der(
1365        private_key_der: &[u8],
1366        password: impl AsRef<[u8]>,
1367    ) -> Result<Self, SigningKeyError> {
1368        let key = P521SigningKey::from_pkcs8_encrypted_der(private_key_der, password)
1369            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1370        Ok(Self { key })
1371    }
1372}
1373
1374impl SigningKey for EcdsaP521SigningKey {
1375    fn sign(
1376        &self,
1377        algorithm: SignatureAlgorithm,
1378        canonical_signed_info: &[u8],
1379    ) -> Result<Vec<u8>, SigningKeyError> {
1380        self.sign_with_provider(
1381            crate::provider::default_provider(),
1382            algorithm,
1383            canonical_signed_info,
1384        )
1385    }
1386
1387    fn sign_with_provider(
1388        &self,
1389        provider: &dyn crate::provider::CryptoProvider,
1390        algorithm: SignatureAlgorithm,
1391        canonical_signed_info: &[u8],
1392    ) -> Result<Vec<u8>, SigningKeyError> {
1393        sign_ecdsa_with_provider::<P521Signature, _>(
1394            &self.key,
1395            provider,
1396            algorithm,
1397            canonical_signed_info,
1398        )
1399    }
1400
1401    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1402        ecdsa_public_key_info(self.key.verifying_key(), EC_P521_OID)
1403    }
1404}
1405
1406/// Select which existing XMLDSig template [`SignContext::sign_template`] signs.
1407#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1408pub enum SignatureTemplateSelection {
1409    /// Select the first descendant template in document order.
1410    FirstDescendant,
1411    /// Select the last descendant template, matching append-then-sign workflows.
1412    #[default]
1413    LastDescendant,
1414}
1415
1416impl SignatureTemplateSelection {
1417    const fn target(self) -> SigningSignatureTarget {
1418        match self {
1419            Self::FirstDescendant => SigningSignatureTarget::First,
1420            Self::LastDescendant => SigningSignatureTarget::Last,
1421        }
1422    }
1423}
1424
1425/// XMLDSig signing context.
1426pub struct SignContext<'a> {
1427    signing_key: &'a dyn SigningKey,
1428    key_info_writer: Option<&'a dyn KeyInfoWriter>,
1429    start_node_id: Option<&'a str>,
1430    id_attributes: &'a [crate::IdAttributeRegistration],
1431    external_resources: Option<&'a HashMap<String, Vec<u8>>>,
1432    template_selection: SignatureTemplateSelection,
1433    policy: crate::policy::SigningPolicy,
1434    provider: &'a dyn crate::provider::CryptoProvider,
1435    xml_backend: crate::XmlBackend,
1436}
1437
1438impl<'a> SignContext<'a> {
1439    /// Create a signing context using the supplied private key.
1440    pub fn new(signing_key: &'a dyn SigningKey) -> Self {
1441        Self {
1442            signing_key,
1443            key_info_writer: None,
1444            start_node_id: None,
1445            id_attributes: &[],
1446            external_resources: None,
1447            template_selection: SignatureTemplateSelection::default(),
1448            policy: crate::policy::SigningPolicy::default(),
1449            provider: crate::provider::default_provider(),
1450            xml_backend: crate::XmlBackend::default(),
1451        }
1452    }
1453
1454    /// Replace the complete immutable signing policy snapshot.
1455    #[must_use]
1456    pub fn policy(mut self, policy: crate::policy::SigningPolicy) -> Self {
1457        self.policy = policy;
1458        self
1459    }
1460
1461    /// Select the cryptographic provider for digest and randomness operations.
1462    #[must_use]
1463    pub fn provider(mut self, provider: &'a dyn crate::provider::CryptoProvider) -> Self {
1464        self.provider = provider;
1465        self
1466    }
1467
1468    /// Select the compiled XML parser backend for this signing operation.
1469    #[must_use]
1470    pub fn xml_backend(mut self, backend: crate::XmlBackend) -> Self {
1471        self.xml_backend = backend;
1472        self
1473    }
1474
1475    fn document_parse_settings(&self) -> DocumentParseSettings {
1476        DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources)
1477            .with_backend(self.xml_backend)
1478    }
1479
1480    /// Configure signing to populate the direct `<Signature>/<KeyInfo>` placeholder.
1481    #[must_use]
1482    pub fn key_info_writer(mut self, writer: &'a dyn KeyInfoWriter) -> Self {
1483        self.key_info_writer = Some(writer);
1484        self
1485    }
1486
1487    /// Select an operation start node by ID and scope template selection to its subtree.
1488    ///
1489    /// [`Self::sign_with_builder`] instead uses the selected node as the append
1490    /// location and signs the newly appended direct `<Signature>` child.
1491    #[must_use]
1492    pub fn start_node_id(mut self, id: &'a str) -> Self {
1493        self.start_node_id = Some(id);
1494        self
1495    }
1496
1497    /// Select which existing `<Signature>` template [`Self::sign_template`] signs.
1498    ///
1499    /// The default is [`SignatureTemplateSelection::LastDescendant`], preserving
1500    /// append-then-sign behavior. Compatibility boundaries that model donor
1501    /// document-order lookup can explicitly select `FirstDescendant`.
1502    #[must_use]
1503    pub fn signature_template_selection(mut self, selection: SignatureTemplateSelection) -> Self {
1504        self.template_selection = selection;
1505        self
1506    }
1507
1508    /// Add caller-declared ID attributes for start-node and Reference lookup.
1509    #[must_use]
1510    pub fn id_attributes(mut self, registrations: &'a [crate::IdAttributeRegistration]) -> Self {
1511        self.id_attributes = registrations;
1512        self
1513    }
1514
1515    /// Provide detached Reference payloads explicitly.
1516    ///
1517    /// The map is the complete external I/O boundary: signing never performs
1518    /// network or filesystem access. External URI classes must also be enabled
1519    /// by [`crate::policy::SigningPolicy::uris`]. Keys are normalized RFC 3986
1520    /// URI identities with dot segments removed and query/fragment retained.
1521    #[must_use]
1522    pub fn external_resources(mut self, resources: &'a HashMap<String, Vec<u8>>) -> Self {
1523        self.external_resources = Some(resources);
1524        self
1525    }
1526
1527    /// Select the node returned by XPath's `here()` extension function.
1528    ///
1529    /// The default follows XMLDSig and returns the `<XPath>` parameter.
1530    /// [`XPathHereSemantics::XmlSecLegacy`] is available only for producing
1531    /// signatures compatible with libxmlsec1's `<Transform>` interpretation.
1532    #[must_use]
1533    pub fn xpath_here_semantics(mut self, semantics: XPathHereSemantics) -> Self {
1534        self.policy.transforms.xpath_here_semantics = semantics;
1535        self
1536    }
1537
1538    /// Sign XML that already contains a `<Signature>` template.
1539    ///
1540    /// The template must include empty `<DigestValue>` and `<SignatureValue>`
1541    /// targets. The pipeline first materializes configured `<KeyInfo>` content,
1542    /// then fills reference digests, reparses the result, canonicalizes
1543    /// `<SignedInfo>`, signs those canonical bytes, and fills the base64
1544    /// `<SignatureValue>`. This ordering permits `<KeyInfo>` to be referenced
1545    /// from `<SignedInfo>` without producing a stale digest.
1546    pub fn sign_template(&self, xml: &str) -> Result<String, SigningError> {
1547        self.policy.validate()?;
1548        self.policy.resources.validate_xml_document_len(xml.len())?;
1549        let mut budgets = SigningOperationBudgets::from_resources_with_backend(
1550            &self.policy.resources,
1551            self.xml_backend,
1552        );
1553        let mut document = XmlDocument::parse_with_settings_and_budget(
1554            xml.to_owned(),
1555            self.document_parse_settings(),
1556            budgets.transforms.xml_parse_work(),
1557        )
1558        .map_err(|error| match owned_document_policy_violation(error) {
1559            Ok(error) => SigningError::Policy(error),
1560            Err(XmlDocumentError::Parse(error)) => {
1561                SigningError::Digest(SigningDigestError::XmlParse(error))
1562            }
1563            Err(error) => SigningError::Document(error),
1564        })?;
1565        self.validate_owned_document_input(&document)?;
1566        self.sign_document_in_place(&mut document, &mut budgets)?;
1567        Ok(document.into_xml())
1568    }
1569
1570    /// Sign a template in a reusable owned document.
1571    ///
1572    /// Signing is atomic: failures leave both serialization and generation
1573    /// unchanged. Success commits the complete signature as one generation.
1574    pub fn sign_document(&self, document: &mut XmlDocument) -> Result<(), SigningError> {
1575        self.validate_owned_document_input(document)?;
1576        let mut budgets = SigningOperationBudgets::from_resources_with_backend(
1577            &self.policy.resources,
1578            self.xml_backend,
1579        );
1580        let mut staged = document
1581            .staged_copy_with_budget(
1582                self.document_parse_settings(),
1583                budgets.transforms.xml_parse_work(),
1584            )
1585            .map_err(map_owned_document_mutation_error)?;
1586        self.sign_document_in_place(&mut staged, &mut budgets)?;
1587        commit_signed_staged(document, staged, &self.policy)
1588    }
1589
1590    fn sign_document_in_place(
1591        &self,
1592        document: &mut XmlDocument,
1593        budgets: &mut SigningOperationBudgets,
1594    ) -> Result<(), SigningError> {
1595        let target_signature = document.with_view(|view| {
1596            signing_signature_index(
1597                view.document(),
1598                self.start_node_id,
1599                self.id_attributes,
1600                self.template_selection,
1601            )
1602        })?;
1603        self.policy.resources.validate_key_candidates(1)?;
1604        self.sign_template_at_index_with_budgets(document, target_signature, budgets)?;
1605        Ok(())
1606    }
1607
1608    fn validate_owned_document_input(&self, document: &XmlDocument) -> Result<(), SigningError> {
1609        self.policy.validate()?;
1610        document.validate_operation_policy(&self.policy.xml, &self.policy.resources)?;
1611        if let Some(resources) = self.external_resources {
1612            validate_external_resource_map(
1613                resources,
1614                self.policy.resources.max_external_resource_bytes,
1615                self.policy.resources.max_external_resource_total_bytes,
1616            )
1617            .map_err(map_signing_external_resource_map_error)?;
1618        }
1619        Ok(())
1620    }
1621
1622    fn sign_template_at_index_with_budgets(
1623        &self,
1624        document: &mut XmlDocument,
1625        target_signature: usize,
1626        budgets: &mut SigningOperationBudgets,
1627    ) -> Result<(), SigningError> {
1628        document.with_view(|view| {
1629            let signature = find_signing_signature_node(
1630                view.document(),
1631                SigningSignatureTarget::Index(target_signature),
1632            )?;
1633            parse_signature_children(signature)
1634                .map_err(|error| SigningDigestError::InvalidStructure(error.to_string()))?;
1635            validate_signing_signed_info_methods(signature, &self.policy)?;
1636            Ok::<_, SigningError>(())
1637        })?;
1638        let transform_options = TransformOptions::default()
1639            .allow_internal_dtd(self.policy.xml.allow_internal_dtd)
1640            .xpath_here_semantics(self.policy.transforms.xpath_here_semantics);
1641        let external_resources = ExternalResourceContext::new(
1642            self.external_resources,
1643            self.policy.resources.max_external_resource_bytes,
1644            self.policy.resources.max_external_resource_total_bytes,
1645        );
1646        let binding = (document.identity(), document.generation());
1647        let mut operation =
1648            OperationExecutionContext::new(self.policy.clone(), &mut *budgets, Some(binding));
1649        let key_info = operation.add_node(
1650            OperationNodeKind::Key { index: 0 },
1651            OperationStage::Resolve,
1652            None,
1653        );
1654        operation.compile()?;
1655        let key_info_content = operation.run(key_info, || {
1656            let Some(writer) = self.key_info_writer else {
1657                return Ok::<_, SigningError>(None);
1658            };
1659            let key_info_content =
1660                writer.write_key_info_with_provider(self.signing_key, self.provider)?;
1661            // Writer output is a separate untrusted XML input. Bound it before
1662            // namespace wrapping or parsing, then bound the merged document below.
1663            self.policy
1664                .resources
1665                .validate_xml_document_len(key_info_content.len())?;
1666            Ok(Some(key_info_content))
1667        })?;
1668        operation.extend();
1669        // Validate reference inputs and build the normalization candidate only after
1670        // independent key output has passed its own byte boundary. The execution pass
1671        // revalidates the resulting document defensively after controlled mutations.
1672        let c14n_candidate = materialize_second_edition_c14n11_candidate(
1673            document,
1674            target_signature,
1675            &self.policy,
1676            external_resources.is_configured(),
1677        )?;
1678        let normalization = if let Some(candidate) = c14n_candidate {
1679            let mutation =
1680                operation.add_node(OperationNodeKind::Mutation, OperationStage::Resolve, None);
1681            operation.add_dependency(mutation, key_info)?;
1682            operation.compile()?;
1683            operation.run_document_transition(mutation, document, |document, budgets| {
1684                document
1685                    .replace_serialized_with_settings(
1686                        candidate,
1687                        self.document_parse_settings(),
1688                        Some(budgets.transforms.xml_parse_work()),
1689                    )
1690                    .map_err(map_owned_document_mutation_error)
1691            })?;
1692            operation.extend();
1693            Some(mutation)
1694        } else {
1695            None
1696        };
1697        let setup_gate = normalization.unwrap_or(key_info);
1698        let setup_gate = if let Some(key_info_content) = key_info_content {
1699            let mutation =
1700                operation.add_node(OperationNodeKind::Mutation, OperationStage::Resolve, None);
1701            operation.add_dependency(mutation, setup_gate)?;
1702            operation.compile()?;
1703            operation.run_document_transition(mutation, document, |document, budgets| {
1704                // The mutation helper checks its namespace wrapper and every projected
1705                // replacement against policy before allocating the committed candidate.
1706                let populated = merge_key_info_source_at_index_with_budget(
1707                    document.as_xml(),
1708                    &key_info_content,
1709                    target_signature,
1710                    Some(&self.policy),
1711                    Some(budgets.transforms.xml_parse_work()),
1712                )?;
1713                self.policy
1714                    .resources
1715                    .validate_xml_document_len(populated.len())?;
1716                document
1717                    .replace_serialized_with_settings(
1718                        populated,
1719                        self.document_parse_settings(),
1720                        Some(budgets.transforms.xml_parse_work()),
1721                    )
1722                    .map_err(map_owned_document_mutation_error)
1723            })?;
1724            operation.extend();
1725            mutation
1726        } else {
1727            setup_gate
1728        };
1729        let plan_nodes = fill_reference_digest_values_in_dependency_order_with_operation(
1730            document,
1731            transform_options,
1732            self.provider,
1733            &mut operation,
1734            SigningReferenceRequest {
1735                target_signature,
1736                id_attributes: self.id_attributes,
1737                external_resources: &external_resources,
1738            },
1739            Some(setup_gate),
1740        )?;
1741        self.policy
1742            .resources
1743            .validate_xml_document_len(document.as_xml().len())?;
1744        let (algorithm, hmac_output_length_bits, canonical_signed_info) = operation
1745            .run_with_budgets(plan_nodes.canonicalization, |budgets| {
1746                let result =
1747                    canonicalize_signed_info(document, &self.policy, budgets, target_signature)?;
1748                budgets
1749                    .transforms
1750                    .charge_c14n_output(result.2.len())
1751                    .map_err(SigningDigestError::Transform)?;
1752                Ok::<_, SigningError>(result)
1753            })?;
1754        self.policy.check_signature_algorithm(algorithm)?;
1755        let expected_signature_len = expected_signature_output_len(
1756            self.signing_key,
1757            algorithm,
1758            &self.policy,
1759            hmac_output_length_bits,
1760        )?;
1761        let projected_signature_len = projected_signature_output_len(
1762            algorithm,
1763            expected_signature_len,
1764            self.policy.ecdsa_signature_value_encoding,
1765        )?;
1766        let encoded_signature_len =
1767            padded_base64_len_for_xml(projected_signature_len, &self.policy)?;
1768        let signature_value_node = document.with_view(|view| {
1769            let signature = find_signing_signature_node(
1770                view.document(),
1771                SigningSignatureTarget::Index(target_signature),
1772            )?;
1773            let signature_value = find_required_child(signature, "SignatureValue")?;
1774            Ok::<_, SigningError>(view.node_identity(signature_value))
1775        })?;
1776        let projected_document_len = document
1777            .projected_content_replacement_len(signature_value_node, encoded_signature_len)?;
1778        self.policy
1779            .resources
1780            .validate_xml_document_len(projected_document_len)?;
1781        let signature_value = operation.run(plan_nodes.crypto, || {
1782            self.provider
1783                .require_capability(crate::provider::ProviderCapability::Sign(algorithm))
1784                .map_err(SigningKeyError::from)?;
1785            let mut signature_value =
1786                self.provider
1787                    .sign(self.signing_key, algorithm, &canonical_signed_info)?;
1788            if algorithm.hmac_output_bits().is_some() {
1789                signature_value.truncate(expected_signature_len);
1790            }
1791            validate_signature_output(expected_signature_len, &signature_value)?;
1792            Ok::<_, SigningError>(signature_value)
1793        })?;
1794        let signature_b64 = operation.run(plan_nodes.evidence, || {
1795            let signature_value = encode_signature_output(
1796                algorithm,
1797                signature_value,
1798                self.policy.ecdsa_signature_value_encoding,
1799            )?;
1800            Ok::<_, SigningError>(base64::engine::general_purpose::STANDARD.encode(signature_value))
1801        })?;
1802        operation.run_document_transition(plan_nodes.mutation, document, |document, budgets| {
1803            document
1804                .replace_base64_contents_with_budget(
1805                    &[(signature_value_node, signature_b64)],
1806                    self.document_parse_settings(),
1807                    budgets.transforms.xml_parse_work(),
1808                )
1809                .map_err(map_owned_document_mutation_error)
1810        })?;
1811        self.policy
1812            .resources
1813            .validate_xml_document_len(document.as_xml().len())?;
1814        Ok(())
1815    }
1816
1817    /// Build a signature template, append it to the selected start node (or
1818    /// the document root when no selector is set), then sign that new template.
1819    pub fn sign_with_builder(
1820        &self,
1821        xml: &str,
1822        builder: &SignatureBuilder,
1823    ) -> Result<String, SigningError> {
1824        self.policy.validate()?;
1825        self.policy.resources.validate_xml_document_len(xml.len())?;
1826        let mut budgets = SigningOperationBudgets::from_resources_with_backend(
1827            &self.policy.resources,
1828            self.xml_backend,
1829        );
1830        let mut document = XmlDocument::parse_with_settings_and_budget(
1831            xml.to_owned(),
1832            self.document_parse_settings(),
1833            budgets.transforms.xml_parse_work(),
1834        )
1835        .map_err(|error| match owned_document_policy_violation(error) {
1836            Ok(error) => SigningError::Policy(error),
1837            Err(XmlDocumentError::Parse(error)) => {
1838                SigningError::XmlMutation(XmlMutationError::XmlParse(error))
1839            }
1840            Err(error) => SigningError::Document(error),
1841        })?;
1842        self.validate_owned_document_input(&document)?;
1843        self.sign_document_with_builder_in_place(&mut document, builder, &mut budgets)?;
1844        Ok(document.into_xml())
1845    }
1846
1847    /// Build, append, and sign a signature in an owned document.
1848    pub fn sign_document_with_builder(
1849        &self,
1850        document: &mut XmlDocument,
1851        builder: &SignatureBuilder,
1852    ) -> Result<(), SigningError> {
1853        self.validate_owned_document_input(document)?;
1854        let mut budgets = SigningOperationBudgets::from_resources_with_backend(
1855            &self.policy.resources,
1856            self.xml_backend,
1857        );
1858        let mut staged = document
1859            .staged_copy_with_budget(
1860                self.document_parse_settings(),
1861                budgets.transforms.xml_parse_work(),
1862            )
1863            .map_err(map_owned_document_mutation_error)?;
1864        self.sign_document_with_builder_in_place(&mut staged, builder, &mut budgets)?;
1865        commit_signed_staged(document, staged, &self.policy)
1866    }
1867
1868    fn sign_document_with_builder_in_place(
1869        &self,
1870        document: &mut XmlDocument,
1871        builder: &SignatureBuilder,
1872        budgets: &mut SigningOperationBudgets,
1873    ) -> Result<(), SigningError> {
1874        self.policy.resources.validate_key_candidates(1)?;
1875        let expected_signature_len = expected_signature_output_len(
1876            self.signing_key,
1877            builder.signature_method(),
1878            &self.policy,
1879            None,
1880        )?;
1881        let template = builder.build_template_with_policy_for_signature_output(
1882            &self.policy,
1883            expected_signature_len,
1884            &budgets.transforms,
1885            &mut budgets.xpath_parse,
1886        )?;
1887        let signature_parent = if let Some(id) = self.start_node_id {
1888            document.with_view(|view| {
1889                let start = signing_start_node(view.document(), id, self.id_attributes)?;
1890                Ok::<_, SigningError>(view.node_identity(start))
1891            })?
1892        } else {
1893            document.with_view(|view| view.root_element())
1894        };
1895        let projected_document_len =
1896            document.projected_child_append_len(signature_parent, template.len())?;
1897        self.policy
1898            .resources
1899            .validate_xml_document_len(projected_document_len)?;
1900        let binding = (document.identity(), document.generation());
1901        let mut operation =
1902            OperationExecutionContext::new(self.policy.clone(), &mut *budgets, Some(binding));
1903        let mutation =
1904            operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
1905        operation.compile()?;
1906        operation.run_document_transition(mutation, document, |document, budgets| {
1907            document
1908                .append_generated_child_with_budget(
1909                    signature_parent,
1910                    &template,
1911                    self.document_parse_settings(),
1912                    budgets.transforms.xml_parse_work(),
1913                )
1914                .map_err(map_owned_document_mutation_error)
1915        })?;
1916        drop(operation);
1917        self.policy
1918            .resources
1919            .validate_xml_document_len(document.as_xml().len())?;
1920        let target_signature = document.with_view(|view| {
1921            let parent = if let Some(id) = self.start_node_id {
1922                signing_start_node(view.document(), id, self.id_attributes)?
1923            } else {
1924                view.document().root_element()
1925            };
1926            let appended = parent
1927                .children()
1928                .rfind(|node| node.has_tag_name((XMLDSIG_NS, "Signature")))
1929                .ok_or(SigningDigestError::MissingElement {
1930                    element: "Signature",
1931                })?;
1932            signature_index(view.document(), appended).map_err(SigningError::from)
1933        })?;
1934        self.sign_template_at_index_with_budgets(document, target_signature, budgets)?;
1935        Ok(())
1936    }
1937}
1938
1939fn projected_signature_output_len(
1940    algorithm: SignatureAlgorithm,
1941    raw_signature_len: usize,
1942    encoding: crate::policy::EcdsaSignatureValueEncoding,
1943) -> Result<usize, SigningError> {
1944    if matches!(
1945        (algorithm, encoding),
1946        (
1947            SignatureAlgorithm::EcdsaSha1
1948                | SignatureAlgorithm::EcdsaSha224
1949                | SignatureAlgorithm::EcdsaSha256
1950                | SignatureAlgorithm::EcdsaSha384
1951                | SignatureAlgorithm::EcdsaSha512,
1952            crate::policy::EcdsaSignatureValueEncoding::XmlSecAsn1Der
1953        )
1954    ) {
1955        return maximum_ecdsa_der_signature_len(raw_signature_len)
1956            .ok_or(SigningKeyError::InvalidPublicKeyInfo.into());
1957    }
1958    Ok(raw_signature_len)
1959}
1960
1961fn map_owned_document_mutation_error(error: XmlDocumentError) -> SigningError {
1962    match owned_document_policy_violation(error) {
1963        Ok(error) => SigningError::Policy(error),
1964        Err(error) => SigningError::Document(error),
1965    }
1966}
1967
1968fn map_owned_document_digest_mutation_error(error: XmlDocumentError) -> SigningDigestError {
1969    match owned_document_policy_violation(error) {
1970        Ok(error) => SigningDigestError::Policy(error),
1971        Err(error) => SigningDigestError::Document(error),
1972    }
1973}
1974
1975fn map_signing_external_resource_map_error(error: ExternalResourceMapError) -> SigningError {
1976    match error {
1977        ExternalResourceMapError::Policy(error) => SigningError::Policy(error),
1978        ExternalResourceMapError::TotalLengthOverflow => {
1979            SigningDigestError::InvalidStructure("external resource total length overflow".into())
1980                .into()
1981        }
1982    }
1983}
1984
1985fn owned_document_policy_violation(
1986    error: XmlDocumentError,
1987) -> Result<crate::policy::PolicyViolation, XmlDocumentError> {
1988    match error {
1989        XmlDocumentError::Policy(error) => Ok(error),
1990        XmlDocumentError::Parse(crate::xml::dom::ParseError::NamespaceBindingLimitReached {
1991            maximum,
1992            actual,
1993        }) => Ok(crate::policy::PolicyViolation::ResourceLimit {
1994            resource: crate::policy::resource_name::XML_NAMESPACE_BINDINGS,
1995            maximum,
1996            actual,
1997        }),
1998        XmlDocumentError::DocumentTooLarge { maximum, actual } => {
1999            Ok(crate::policy::PolicyViolation::ResourceLimit {
2000                resource: crate::policy::resource_name::XML_DOCUMENT,
2001                maximum,
2002                actual,
2003            })
2004        }
2005        XmlDocumentError::DocumentTooDeep { maximum, actual } => {
2006            Ok(crate::policy::PolicyViolation::ResourceLimit {
2007                resource: crate::policy::resource_name::XML_DEPTH,
2008                maximum,
2009                actual,
2010            })
2011        }
2012        XmlDocumentError::ProjectedNodeLimit { maximum } => {
2013            Ok(crate::policy::PolicyViolation::ResourceLimit {
2014                resource: crate::policy::resource_name::XML_NODES,
2015                maximum,
2016                actual: maximum.saturating_add(1),
2017            })
2018        }
2019        error => Err(error),
2020    }
2021}
2022
2023fn encode_signature_output(
2024    algorithm: SignatureAlgorithm,
2025    signature: Vec<u8>,
2026    encoding: crate::policy::EcdsaSignatureValueEncoding,
2027) -> Result<Vec<u8>, SigningError> {
2028    if matches!(
2029        (algorithm, encoding),
2030        (
2031            SignatureAlgorithm::EcdsaSha1
2032                | SignatureAlgorithm::EcdsaSha224
2033                | SignatureAlgorithm::EcdsaSha256
2034                | SignatureAlgorithm::EcdsaSha384
2035                | SignatureAlgorithm::EcdsaSha512,
2036            crate::policy::EcdsaSignatureValueEncoding::XmlSecAsn1Der
2037        )
2038    ) {
2039        return encode_ecdsa_signature_as_der(&signature)
2040            .ok_or(SigningKeyError::InvalidPublicKeyInfo.into());
2041    }
2042    Ok(signature)
2043}
2044
2045#[derive(Debug, Clone)]
2046struct SigningReference {
2047    uri: String,
2048    origin_node_id: NodeId,
2049    transforms: Vec<Transform>,
2050    digest_method: DigestAlgorithm,
2051    digest_value_range: Range<usize>,
2052    digest_value_node_id: NodeId,
2053}
2054
2055struct SigningOperationBudgets {
2056    transforms: TransformExecutionBudget,
2057    xpath_parse: XPathSignatureParseBudget,
2058}
2059
2060struct SigningReferenceRequest<'a, 'resources> {
2061    target_signature: usize,
2062    id_attributes: &'a [crate::IdAttributeRegistration],
2063    external_resources: &'a ExternalResourceContext<'resources>,
2064}
2065
2066struct SigningPlanNodes {
2067    canonicalization: OperationNodeId,
2068    crypto: OperationNodeId,
2069    evidence: OperationNodeId,
2070    mutation: OperationNodeId,
2071}
2072
2073impl SigningOperationBudgets {
2074    fn from_resources(resources: &crate::policy::ResourcePolicy) -> Self {
2075        Self {
2076            transforms: TransformExecutionBudget::from_resources(resources),
2077            xpath_parse: XPathSignatureParseBudget::from_resources(resources),
2078        }
2079    }
2080
2081    fn from_resources_with_backend(
2082        resources: &crate::policy::ResourcePolicy,
2083        backend: crate::XmlBackend,
2084    ) -> Self {
2085        Self {
2086            transforms: TransformExecutionBudget::from_resources(resources)
2087                .with_xml_backend(backend),
2088            xpath_parse: XPathSignatureParseBudget::from_resources(resources),
2089        }
2090    }
2091}
2092
2093impl Default for SigningOperationBudgets {
2094    fn default() -> Self {
2095        Self::from_resources(&crate::policy::ResourcePolicy::default())
2096    }
2097}
2098
2099/// Compute base64 digest values for every `<Reference>` in the signing template.
2100///
2101/// References are processed in `<SignedInfo>` document order under the last
2102/// XMLDSig `<Signature>` element. `sign_with_builder()` appends a new template
2103/// at the end of the source root, so older signatures in an already-signed
2104/// document must not become the signing target.
2105pub fn compute_reference_digest_values(
2106    xml: &str,
2107) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2108    let execution_budget = TransformExecutionBudget::default();
2109    compute_reference_digest_values_with_options(
2110        xml,
2111        TransformOptions::default(),
2112        None,
2113        crate::provider::default_provider(),
2114        &execution_budget,
2115        None,
2116        &[],
2117    )
2118}
2119
2120fn compute_reference_digest_values_with_options(
2121    xml: &str,
2122    transform_options: TransformOptions,
2123    policy: Option<&crate::policy::SigningPolicy>,
2124    provider: &dyn crate::provider::CryptoProvider,
2125    execution_budget: &TransformExecutionBudget,
2126    target_signature: Option<usize>,
2127    id_attributes: &[crate::IdAttributeRegistration],
2128) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2129    let (prepared, target_signature) =
2130        prepare_reference_digest_input(xml, policy, execution_budget, target_signature)?;
2131    compute_prepared_reference_digest_values_with_options(
2132        prepared.as_ref(),
2133        transform_options,
2134        policy,
2135        provider,
2136        execution_budget,
2137        target_signature,
2138        id_attributes,
2139    )
2140}
2141
2142fn compute_prepared_reference_digest_values_with_options(
2143    xml: &str,
2144    transform_options: TransformOptions,
2145    policy: Option<&crate::policy::SigningPolicy>,
2146    provider: &dyn crate::provider::CryptoProvider,
2147    execution_budget: &TransformExecutionBudget,
2148    target_signature: usize,
2149    id_attributes: &[crate::IdAttributeRegistration],
2150) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2151    let resource_policy = policy
2152        .map(|policy| &policy.resources)
2153        .cloned()
2154        .unwrap_or_default();
2155    let external_resources = ExternalResourceContext::new(
2156        None,
2157        resource_policy.max_external_resource_bytes,
2158        resource_policy.max_external_resource_total_bytes,
2159    );
2160    let doc = parse_signing_document(
2161        xml,
2162        policy,
2163        execution_budget.xml_parse_work(),
2164        crate::XmlBackend::default(),
2165    )?;
2166    let signature =
2167        find_signing_signature_node(&doc, SigningSignatureTarget::Index(target_signature))?;
2168    let signed_info = find_required_child(signature, "SignedInfo")?;
2169    let references = parse_signing_references(signed_info)?;
2170    validate_signing_references(&references, references.len(), policy, false)?;
2171    compute_signing_reference_digests(
2172        &doc,
2173        signature,
2174        references,
2175        transform_options,
2176        provider,
2177        execution_budget,
2178        SigningUriResolution {
2179            id_attributes,
2180            same_document_id_semantics: policy.map_or(
2181                crate::policy::SameDocumentIdSemantics::Specification,
2182                |policy| policy.transforms.same_document_id_semantics,
2183            ),
2184            external_resources: &external_resources,
2185        },
2186    )
2187}
2188
2189fn prepare_reference_digest_input<'a>(
2190    xml: &'a str,
2191    policy: Option<&crate::policy::SigningPolicy>,
2192    execution_budget: &TransformExecutionBudget,
2193    target_signature: Option<usize>,
2194) -> Result<(Cow<'a, str>, usize), SigningDigestError> {
2195    let default_policy = crate::policy::SigningPolicy::default();
2196    let effective_policy = policy.unwrap_or(&default_policy);
2197    let settings =
2198        DocumentParseSettings::from_policy(&effective_policy.xml, &effective_policy.resources);
2199    let document = XmlDocument::parse_with_settings_and_budget(
2200        xml.to_owned(),
2201        settings,
2202        execution_budget.xml_parse_work(),
2203    )
2204    .map_err(map_owned_document_digest_mutation_error)?;
2205    let target_signature = if let Some(target_signature) = target_signature {
2206        target_signature
2207    } else {
2208        document.with_view(|view| {
2209            let selected =
2210                find_signing_signature_node(view.document(), SigningSignatureTarget::Last)?;
2211            signature_index(view.document(), selected)
2212        })?
2213    };
2214    // Preserve the public compute helper's precise pre-policy errors before
2215    // applying the default generation policy to the normalized candidate.
2216    document.with_view(|view| {
2217        let signature = find_signing_signature_node(
2218            view.document(),
2219            SigningSignatureTarget::Index(target_signature),
2220        )?;
2221        let signed_info = find_required_child(signature, "SignedInfo")?;
2222        let references = parse_signing_references(signed_info)?;
2223        validate_signing_references(&references, references.len(), policy, false)
2224    })?;
2225    let materialized = materialize_second_edition_c14n11_candidate(
2226        &document,
2227        target_signature,
2228        effective_policy,
2229        false,
2230    )?;
2231    Ok((
2232        materialized.map_or(Cow::Borrowed(xml), Cow::Owned),
2233        target_signature,
2234    ))
2235}
2236
2237fn fill_reference_digest_values_in_dependency_order_with_operation(
2238    document: &mut XmlDocument,
2239    transform_options: TransformOptions,
2240    provider: &dyn crate::provider::CryptoProvider,
2241    operation: &mut OperationExecutionContext<
2242        crate::policy::SigningPolicy,
2243        &mut SigningOperationBudgets,
2244    >,
2245    request: SigningReferenceRequest<'_, '_>,
2246    setup_gate: Option<OperationNodeId>,
2247) -> Result<SigningPlanNodes, SigningDigestError> {
2248    let policy = operation.policy().clone();
2249    let reference_limit = policy
2250        .resources
2251        .max_references
2252        .min(MAX_REFERENCES_PER_SIGNATURE);
2253    let process_manifests =
2254        policy.manifest_processing == crate::policy::ManifestProcessing::Process;
2255    let (signed_info_references, manifest_references) = {
2256        let budgets = operation.budgets_mut();
2257        document.with_view(|view| {
2258            let signature = find_signing_signature_node(
2259                view.document(),
2260                SigningSignatureTarget::Index(request.target_signature),
2261            )?;
2262            let signed_info = find_required_child(signature, "SignedInfo")?;
2263            let signed_info_references =
2264                parse_signing_references_with_budget(signed_info, &mut budgets.xpath_parse)?;
2265            validate_signing_references(
2266                &signed_info_references,
2267                signed_info_references.len(),
2268                Some(&policy),
2269                request.external_resources.is_configured(),
2270            )?;
2271            let manifest_references = if process_manifests {
2272                parse_signing_manifest_references(
2273                    signature,
2274                    &mut budgets.xpath_parse,
2275                    reference_limit.saturating_sub(signed_info_references.len()),
2276                    reference_limit,
2277                )?
2278            } else {
2279                Vec::new()
2280            };
2281            Ok::<_, SigningDigestError>((signed_info_references, manifest_references))
2282        })?
2283    };
2284    let total_references = signed_info_references
2285        .len()
2286        .checked_add(manifest_references.len())
2287        .ok_or_else(|| SigningDigestError::InvalidStructure("reference count overflow".into()))?;
2288    validate_signing_references(
2289        &manifest_references,
2290        total_references,
2291        Some(&policy),
2292        request.external_resources.is_configured(),
2293    )?;
2294    let placeholder = "AA==";
2295    // SignatureValue is the final mutable value in the signing pipeline. Give
2296    // it concrete character data during analysis so references that retain the
2297    // existing or future text cannot be mistaken for stable inputs.
2298    let analysis_replacements = document.with_view(|view| {
2299        let signature = find_signing_signature_node(
2300            view.document(),
2301            SigningSignatureTarget::Index(request.target_signature),
2302        )?;
2303        let signature_value = find_required_child(signature, "SignatureValue")?;
2304        let mut replacements = signed_info_references
2305            .iter()
2306            .chain(&manifest_references)
2307            .map(|reference| {
2308                (
2309                    view.node_identity_by_id(reference.digest_value_node_id),
2310                    placeholder.to_owned(),
2311                )
2312            })
2313            .collect::<Vec<_>>();
2314        replacements.push((view.node_identity(signature_value), placeholder.to_owned()));
2315        Ok::<_, SigningDigestError>(replacements)
2316    })?;
2317    let analysis_xml = document
2318        .project_base64_contents(
2319            &analysis_replacements,
2320            policy.resources.max_xml_document_bytes,
2321        )
2322        .map_err(map_owned_document_digest_mutation_error)?;
2323    let analysis_doc = parse_signing_document(
2324        &analysis_xml,
2325        Some(&policy),
2326        operation.budgets().transforms.xml_parse_work(),
2327        document.xml_backend(),
2328    )?;
2329    let analysis_signature = find_signing_signature_node(
2330        &analysis_doc,
2331        SigningSignatureTarget::Index(request.target_signature),
2332    )?;
2333    let analysis_signed_info = find_required_child(analysis_signature, "SignedInfo")?;
2334    let mut analysis_references = parse_signing_references_with_budget(
2335        analysis_signed_info,
2336        &mut operation.budgets_mut().xpath_parse,
2337    )?;
2338    if process_manifests {
2339        analysis_references.extend(parse_signing_manifest_references(
2340            analysis_signature,
2341            &mut operation.budgets_mut().xpath_parse,
2342            reference_limit.saturating_sub(signed_info_references.len()),
2343            reference_limit,
2344        )?);
2345    }
2346    let dependency_plan = reference_dependency_levels(
2347        &analysis_doc,
2348        analysis_signature,
2349        &analysis_references,
2350        transform_options,
2351        &operation.budgets().transforms,
2352        SigningUriResolution {
2353            id_attributes: request.id_attributes,
2354            same_document_id_semantics: policy.transforms.same_document_id_semantics,
2355            external_resources: request.external_resources,
2356        },
2357    )?;
2358    let (node_levels, plan_nodes) =
2359        compile_signing_operation_plan(operation, &dependency_plan, setup_gate)?;
2360    for (level, node_level) in dependency_plan.levels.into_iter().zip(node_levels) {
2361        operation.run_document_transition_batch_with_budgets(
2362            &node_level,
2363            document,
2364            |document, budgets| {
2365                let replacements = document.with_view(|view| {
2366                    let current_doc = view.document();
2367                    let current_signature = find_signing_signature_node(
2368                        current_doc,
2369                        SigningSignatureTarget::Index(request.target_signature),
2370                    )?;
2371                    let current_signed_info = find_required_child(current_signature, "SignedInfo")?;
2372                    let current_signed_info_references = parse_signing_references_with_budget(
2373                        current_signed_info,
2374                        &mut budgets.xpath_parse,
2375                    )?;
2376                    let current_manifest_references = if process_manifests {
2377                        parse_signing_manifest_references(
2378                            current_signature,
2379                            &mut budgets.xpath_parse,
2380                            reference_limit.saturating_sub(signed_info_references.len()),
2381                            reference_limit,
2382                        )?
2383                    } else {
2384                        Vec::new()
2385                    };
2386                    if current_signed_info_references.len() != signed_info_references.len()
2387                        || current_manifest_references.len() != manifest_references.len()
2388                    {
2389                        return Err(SigningDigestError::InvalidStructure(
2390                            "signing Reference set changed while filling digests".into(),
2391                        ));
2392                    }
2393                    let mut destinations = Vec::with_capacity(level.len());
2394                    let mut level_references = Vec::with_capacity(level.len());
2395                    for index in &level {
2396                        let reference = if *index < signed_info_references.len() {
2397                            current_signed_info_references.get(*index)
2398                        } else {
2399                            current_manifest_references.get(*index - signed_info_references.len())
2400                        }
2401                        .ok_or_else(|| {
2402                            SigningDigestError::InvalidStructure(
2403                                "signing Reference set changed while filling digests".into(),
2404                            )
2405                        })?;
2406                        destinations.push(view.node_identity_by_id(reference.digest_value_node_id));
2407                        level_references.push(reference.clone());
2408                    }
2409                    let computed = compute_signing_reference_digests(
2410                        current_doc,
2411                        current_signature,
2412                        level_references,
2413                        transform_options,
2414                        provider,
2415                        &budgets.transforms,
2416                        SigningUriResolution {
2417                            id_attributes: request.id_attributes,
2418                            same_document_id_semantics: policy
2419                                .transforms
2420                                .same_document_id_semantics,
2421                            external_resources: request.external_resources,
2422                        },
2423                    )?;
2424                    if computed.len() != destinations.len() {
2425                        return Err(SigningDigestError::InvalidStructure(
2426                            "signing Reference set changed while computing digests".into(),
2427                        ));
2428                    }
2429                    Ok::<_, SigningDigestError>(
2430                        destinations
2431                            .into_iter()
2432                            .zip(computed)
2433                            .map(|(target, digest)| (target, digest.digest_value))
2434                            .collect::<Vec<_>>(),
2435                    )
2436                })?;
2437                document
2438                    .replace_base64_contents_with_budget(
2439                        &replacements,
2440                        DocumentParseSettings::from_policy(&policy.xml, &policy.resources)
2441                            .with_backend(document.xml_backend()),
2442                        budgets.transforms.xml_parse_work(),
2443                    )
2444                    .map_err(map_owned_document_digest_mutation_error)
2445            },
2446        )?;
2447    }
2448    Ok(plan_nodes)
2449}
2450
2451#[cfg(test)]
2452fn fill_reference_digest_values_in_dependency_order(
2453    document: &mut XmlDocument,
2454    transform_options: TransformOptions,
2455    policy: &crate::policy::SigningPolicy,
2456    provider: &dyn crate::provider::CryptoProvider,
2457    budgets: &mut SigningOperationBudgets,
2458    target_signature: usize,
2459    id_attributes: &[crate::IdAttributeRegistration],
2460) -> Result<(), SigningDigestError> {
2461    let external_resources = ExternalResourceContext::new(
2462        None,
2463        policy.resources.max_external_resource_bytes,
2464        policy.resources.max_external_resource_total_bytes,
2465    );
2466    let binding = (document.identity(), document.generation());
2467    let mut operation = OperationExecutionContext::new(policy.clone(), budgets, Some(binding));
2468    fill_reference_digest_values_in_dependency_order_with_operation(
2469        document,
2470        transform_options,
2471        provider,
2472        &mut operation,
2473        SigningReferenceRequest {
2474            target_signature,
2475            id_attributes,
2476            external_resources: &external_resources,
2477        },
2478        None,
2479    )
2480    .map(|_| ())
2481}
2482
2483struct SigningDependencyPlan {
2484    dependencies: Vec<HashSet<usize>>,
2485    levels: Vec<Vec<usize>>,
2486}
2487
2488fn reference_dependency_levels(
2489    doc: &Document<'_>,
2490    signature: Node<'_, '_>,
2491    references: &[SigningReference],
2492    transform_options: TransformOptions,
2493    execution_budget: &TransformExecutionBudget,
2494    uri_resolution: SigningUriResolution<'_, '_>,
2495) -> Result<SigningDependencyPlan, SigningDigestError> {
2496    let resolver = uri_resolution.external_resources.bind(
2497        doc,
2498        uri_resolution.id_attributes,
2499        uri_resolution.same_document_id_semantics,
2500    );
2501    let terminal_signature_value_index = references.len();
2502    let signature_value = find_required_child(signature, "SignatureValue")?;
2503    let mut tracked_mutable_nodes = references
2504        .iter()
2505        .enumerate()
2506        .flat_map(|(index, reference)| {
2507            std::iter::once((index, reference.digest_value_node_id)).chain(
2508                doc.get_node(reference.digest_value_node_id)
2509                    .into_iter()
2510                    .flat_map(|node| node.children())
2511                    .filter(|node| node.is_text())
2512                    .map(move |node| (index, node.id())),
2513            )
2514        })
2515        .collect::<Vec<_>>();
2516    tracked_mutable_nodes.push((terminal_signature_value_index, signature_value.id()));
2517    tracked_mutable_nodes.extend(
2518        signature_value
2519            .children()
2520            .filter(|node| node.is_text())
2521            .map(|node| (terminal_signature_value_index, node.id())),
2522    );
2523    let analyses = references
2524        .iter()
2525        .map(|reference| {
2526            let origin = signing_reference_origin(doc, reference)?;
2527            let initial_data = resolver.dereference_from_with_budget(
2528                &reference.uri,
2529                origin,
2530                execution_budget.node_set_materialization(),
2531                execution_budget.xml_base_resolution(),
2532            )?;
2533            let output = execute_transforms_with_dependency_nodes(
2534                signature,
2535                initial_data,
2536                &reference.transforms,
2537                transform_options,
2538                execution_budget,
2539                tracked_mutable_nodes.clone(),
2540            )?;
2541            Ok(output.dependencies)
2542        })
2543        .collect::<Result<Vec<_>, SigningDigestError>>()?;
2544    if analyses
2545        .iter()
2546        .any(|dependencies| dependencies.contains(&terminal_signature_value_index))
2547    {
2548        return Err(SigningDigestError::InvalidStructure(
2549            "Reference dependency cycle includes the mutable SignatureValue".into(),
2550        ));
2551    }
2552    let mut remaining_dependencies = analyses.clone();
2553    let mut completed = vec![false; references.len()];
2554    let mut levels = Vec::new();
2555    while completed.iter().any(|done| !done) {
2556        let ready = remaining_dependencies
2557            .iter()
2558            .enumerate()
2559            .filter_map(|(index, dependencies)| {
2560                (!completed[index] && dependencies.is_empty()).then_some(index)
2561            })
2562            .collect::<Vec<_>>();
2563        if ready.is_empty() {
2564            return Err(SigningDigestError::InvalidStructure(
2565                "Manifest Reference digest dependency cycle".into(),
2566            ));
2567        }
2568        for index in &ready {
2569            completed[*index] = true;
2570        }
2571        for dependency_set in &mut remaining_dependencies {
2572            dependency_set.retain(|dependency| !completed[*dependency]);
2573        }
2574        levels.push(ready);
2575    }
2576
2577    // Every mutable DigestValue belongs to the selected Signature;
2578    // an enveloped transform excludes that complete subtree and therefore
2579    // removes all such dependencies from its input node-set.
2580    debug_assert!(references.iter().all(|reference| {
2581        signature.range().start <= reference.digest_value_range.start
2582            && signature.range().end >= reference.digest_value_range.end
2583    }));
2584    Ok(SigningDependencyPlan {
2585        dependencies: analyses,
2586        levels,
2587    })
2588}
2589
2590fn signing_reference_origin<'doc, 'input>(
2591    doc: &'doc Document<'input>,
2592    reference: &SigningReference,
2593) -> Result<Node<'doc, 'input>, SigningDigestError> {
2594    doc.get_node(reference.origin_node_id).ok_or_else(|| {
2595        SigningDigestError::InvalidStructure(
2596            "signing Reference origin changed while computing digests".into(),
2597        )
2598    })
2599}
2600
2601fn compile_signing_operation_plan(
2602    operation: &mut OperationExecutionContext<
2603        crate::policy::SigningPolicy,
2604        &mut SigningOperationBudgets,
2605    >,
2606    dependency_plan: &SigningDependencyPlan,
2607    setup_gate: Option<OperationNodeId>,
2608) -> Result<(Vec<Vec<OperationNodeId>>, SigningPlanNodes), SigningDigestError> {
2609    let reference_count = dependency_plan.levels.iter().map(Vec::len).sum::<usize>();
2610    let mut digest_nodes = vec![None; reference_count];
2611    for (index, digest_slot) in digest_nodes.iter_mut().enumerate() {
2612        let digest_node = operation.add_node(
2613            OperationNodeKind::Digest { index },
2614            OperationStage::Digest,
2615            None,
2616        );
2617        if let Some(setup_gate) = setup_gate {
2618            operation
2619                .add_dependency(digest_node, setup_gate)
2620                .map_err(map_signing_digest_plan_error)?;
2621        }
2622        *digest_slot = Some(digest_node);
2623    }
2624
2625    let mut node_levels = Vec::with_capacity(dependency_plan.levels.len());
2626    for level in &dependency_plan.levels {
2627        let nodes = level
2628            .iter()
2629            .map(|index| {
2630                digest_nodes
2631                    .get(*index)
2632                    .and_then(|node| *node)
2633                    .ok_or_else(|| {
2634                        SigningDigestError::InvalidStructure(
2635                            "compiled signing Reference index is unavailable".into(),
2636                        )
2637                    })
2638            })
2639            .collect::<Result<Vec<_>, _>>()?;
2640        for (index, node) in level.iter().zip(&nodes) {
2641            for dependency_index in &dependency_plan.dependencies[*index] {
2642                let dependency = digest_nodes
2643                    .get(*dependency_index)
2644                    .and_then(|node| *node)
2645                    .ok_or_else(|| {
2646                        SigningDigestError::InvalidStructure(
2647                            "compiled signing dependency index is unavailable".into(),
2648                        )
2649                    })?;
2650                operation
2651                    .add_dependency(*node, dependency)
2652                    .map_err(map_signing_digest_plan_error)?;
2653            }
2654        }
2655        node_levels.push(nodes);
2656    }
2657
2658    let canonicalization = operation.add_node(
2659        OperationNodeKind::Canonicalization,
2660        OperationStage::Canonicalization,
2661        None,
2662    );
2663    for digest in digest_nodes.iter().flatten() {
2664        operation
2665            .add_dependency(canonicalization, *digest)
2666            .map_err(map_signing_digest_plan_error)?;
2667    }
2668    let crypto = operation.add_node(OperationNodeKind::Crypto, OperationStage::Crypto, None);
2669    operation
2670        .add_dependency(crypto, canonicalization)
2671        .map_err(map_signing_digest_plan_error)?;
2672    let evidence = operation.add_node(OperationNodeKind::Evidence, OperationStage::Evidence, None);
2673    operation
2674        .add_dependency(evidence, crypto)
2675        .map_err(map_signing_digest_plan_error)?;
2676    let mutation = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
2677    operation
2678        .add_dependency(mutation, evidence)
2679        .map_err(map_signing_digest_plan_error)?;
2680    operation.compile().map_err(map_signing_digest_plan_error)?;
2681
2682    Ok((
2683        node_levels,
2684        SigningPlanNodes {
2685            canonicalization,
2686            crypto,
2687            evidence,
2688            mutation,
2689        },
2690    ))
2691}
2692
2693fn map_signing_digest_plan_error(error: OperationPlanError) -> SigningDigestError {
2694    SigningDigestError::InvalidStructure(error.to_string())
2695}
2696
2697fn commit_signed_staged(
2698    document: &mut XmlDocument,
2699    staged: XmlDocument,
2700    policy: &crate::policy::SigningPolicy,
2701) -> Result<(), SigningError> {
2702    let mut operation = OperationExecutionContext::new(
2703        policy.clone(),
2704        (),
2705        Some((document.identity(), document.generation())),
2706    );
2707    let mutation = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
2708    operation.compile()?;
2709    operation.run_document_transition(mutation, document, |document, _| {
2710        document
2711            .commit_staged(staged)
2712            .map_err(map_owned_document_mutation_error)
2713    })
2714}
2715
2716fn validate_signing_references(
2717    references: &[SigningReference],
2718    total_references: usize,
2719    policy: Option<&crate::policy::SigningPolicy>,
2720    has_external_resources: bool,
2721) -> Result<(), SigningDigestError> {
2722    if let Some(policy) = policy
2723        && total_references > policy.resources.max_references
2724    {
2725        return Err(crate::policy::PolicyViolation::ResourceLimit {
2726            resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
2727            maximum: policy.resources.max_references,
2728            actual: total_references,
2729        }
2730        .into());
2731    }
2732    for reference in references {
2733        if let Some(policy) = policy {
2734            validate_signing_reference_uri(&reference.uri, policy)?;
2735            validate_signing_reference_request(&reference.uri, has_external_resources)?;
2736        }
2737        if let Some(policy) = policy
2738            && reference.transforms.len() > policy.resources.max_transforms_per_reference
2739        {
2740            return Err(crate::policy::PolicyViolation::ResourceLimit {
2741                resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
2742                maximum: policy.resources.max_transforms_per_reference,
2743                actual: reference.transforms.len(),
2744            }
2745            .into());
2746        }
2747        if let Some(policy) = policy {
2748            policy.check_digest_algorithm(reference.digest_method)?;
2749        } else if !reference.digest_method.signing_allowed() {
2750            return Err(SigningDigestError::SigningAlgorithmDisabled {
2751                uri: reference.digest_method.uri(),
2752            });
2753        }
2754        let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
2755        validate_signing_transform_policy(
2756            initial_binary,
2757            &reference.transforms,
2758            policy.and_then(|policy| policy.transforms.allowed_algorithms.as_ref()),
2759        )?;
2760    }
2761    Ok(())
2762}
2763
2764fn validate_signing_signed_info_methods(
2765    signature: Node<'_, '_>,
2766    policy: &crate::policy::SigningPolicy,
2767) -> Result<(), SigningDigestError> {
2768    let signed_info = find_required_child(signature, "SignedInfo")?;
2769    let canonicalization_method =
2770        element_children(signed_info)
2771            .next()
2772            .ok_or(SigningDigestError::MissingElement {
2773                element: "CanonicalizationMethod",
2774            })?;
2775    verify_ds_element(canonicalization_method, "CanonicalizationMethod")?;
2776    let algorithm = required_algorithm_attr(canonicalization_method, "CanonicalizationMethod")?;
2777    if policy
2778        .transforms
2779        .allowed_algorithms
2780        .as_ref()
2781        .is_some_and(|allowed| !allowed.contains(algorithm))
2782    {
2783        return Err(crate::policy::PolicyViolation::Algorithm {
2784            operation: "SignedInfo canonicalization",
2785            algorithm: algorithm.to_owned(),
2786        }
2787        .into());
2788    }
2789    Ok(())
2790}
2791
2792struct SigningUriResolution<'a, 'resources> {
2793    id_attributes: &'a [crate::IdAttributeRegistration],
2794    same_document_id_semantics: crate::policy::SameDocumentIdSemantics,
2795    external_resources: &'a ExternalResourceContext<'resources>,
2796}
2797
2798fn compute_signing_reference_digests(
2799    doc: &Document<'_>,
2800    signature: Node<'_, '_>,
2801    references: Vec<SigningReference>,
2802    transform_options: TransformOptions,
2803    provider: &dyn crate::provider::CryptoProvider,
2804    execution_budget: &TransformExecutionBudget,
2805    uri_resolution: SigningUriResolution<'_, '_>,
2806) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2807    let resolver = uri_resolution.external_resources.bind(
2808        doc,
2809        uri_resolution.id_attributes,
2810        uri_resolution.same_document_id_semantics,
2811    );
2812    references
2813        .into_iter()
2814        .enumerate()
2815        .map(|(index, reference)| {
2816            let origin = signing_reference_origin(doc, &reference)?;
2817            let initial_data = resolver.dereference_from_with_budget(
2818                &reference.uri,
2819                origin,
2820                execution_budget.node_set_materialization(),
2821                execution_budget.xml_base_resolution(),
2822            )?;
2823            let pre_digest = execute_transforms_with_options_and_budget(
2824                signature,
2825                initial_data,
2826                &reference.transforms,
2827                transform_options,
2828                execution_budget,
2829            )?;
2830            let digest = super::compute_digest_with_provider(
2831                provider,
2832                reference.digest_method,
2833                &pre_digest,
2834            )?;
2835            let digest_value = base64::engine::general_purpose::STANDARD.encode(digest);
2836            Ok(ComputedReferenceDigest {
2837                index,
2838                uri: reference.uri,
2839                digest_method: reference.digest_method,
2840                digest_value,
2841            })
2842        })
2843        .collect()
2844}
2845
2846/// Compute and fill all signing-template `<DigestValue>` elements.
2847///
2848/// This is the signing counterpart to verification reference processing: it
2849/// dereferences each `<Reference>`, applies transforms, computes the digest,
2850/// and writes the base64 digest into the matching `<DigestValue>` in document
2851/// order. As required for Second Edition generation, any reference chain that
2852/// still produces a node-set is first made explicit with Canonical XML 1.1.
2853pub fn fill_reference_digest_values(xml: &str) -> Result<String, SigningDigestError> {
2854    let execution_budget = TransformExecutionBudget::default();
2855    fill_reference_digest_values_with_options(
2856        xml,
2857        TransformOptions::default(),
2858        None,
2859        crate::provider::default_provider(),
2860        &execution_budget,
2861        None,
2862        &[],
2863    )
2864}
2865
2866fn fill_reference_digest_values_with_options(
2867    xml: &str,
2868    transform_options: TransformOptions,
2869    policy: Option<&crate::policy::SigningPolicy>,
2870    provider: &dyn crate::provider::CryptoProvider,
2871    execution_budget: &TransformExecutionBudget,
2872    target_signature: Option<usize>,
2873    id_attributes: &[crate::IdAttributeRegistration],
2874) -> Result<String, SigningDigestError> {
2875    let default_policy = crate::policy::SigningPolicy::default();
2876    let effective_policy = policy.unwrap_or(&default_policy);
2877    let (prepared, target_signature) =
2878        prepare_reference_digest_input(xml, policy, execution_budget, target_signature)?;
2879    let digest_values = compute_prepared_reference_digest_values_with_options(
2880        prepared.as_ref(),
2881        transform_options,
2882        Some(effective_policy),
2883        provider,
2884        execution_budget,
2885        target_signature,
2886        id_attributes,
2887    )?
2888    .into_iter()
2889    .map(|digest| digest.digest_value);
2890    Ok(fill_signed_info_digest_values_at_index_with_budget(
2891        prepared.as_ref(),
2892        digest_values,
2893        target_signature,
2894        Some(effective_policy),
2895        Some(execution_budget.xml_parse_work()),
2896    )?)
2897}
2898
2899fn canonicalize_signed_info(
2900    document: &XmlDocument,
2901    policy: &crate::policy::SigningPolicy,
2902    budgets: &mut SigningOperationBudgets,
2903    target_signature: usize,
2904) -> Result<(SignatureAlgorithm, Option<usize>, Vec<u8>), SigningError> {
2905    document.with_view(|view| {
2906        let doc = view.document();
2907        let signature =
2908            find_signing_signature_node(doc, SigningSignatureTarget::Index(target_signature))
2909                .map_err(SigningError::Digest)?;
2910        let signed_info_node =
2911            find_required_child(signature, "SignedInfo").map_err(SigningError::Digest)?;
2912        let signed_info =
2913            parse_signed_info_with_xpath_budget(signed_info_node, &mut budgets.xpath_parse)?;
2914        if policy
2915            .transforms
2916            .allowed_algorithms
2917            .as_ref()
2918            .is_some_and(|allowed| !allowed.contains(signed_info.c14n_method.uri()))
2919        {
2920            return Err(crate::policy::PolicyViolation::Algorithm {
2921                operation: "SignedInfo canonicalization",
2922                algorithm: signed_info.c14n_method.uri().to_owned(),
2923            }
2924            .into());
2925        }
2926        let signed_info_subtree: HashSet<_> = signed_info_node
2927            .descendants()
2928            .map(|node: Node<'_, '_>| node.id())
2929            .collect();
2930        let mut canonical_signed_info = Vec::new();
2931        canonicalize_bounded_with_xml_base_budget(
2932            doc,
2933            Some(&|node| signed_info_subtree.contains(&node.id())),
2934            &signed_info.c14n_method,
2935            budgets.transforms.remaining_c14n_output(),
2936            budgets.transforms.xml_base_resolution(),
2937            &mut canonical_signed_info,
2938        )
2939        .map_err(|error| {
2940            if let Some(violation) = map_c14n_resource_policy_violation(
2941                &error,
2942                crate::policy::resource_name::CANONICALIZED_BYTES,
2943                budgets.transforms.c14n_output_limit(),
2944            ) {
2945                SigningError::Policy(violation)
2946            } else {
2947                SigningError::Canonicalization(error)
2948            }
2949        })?;
2950        Ok((
2951            signed_info.signature_method,
2952            signed_info.hmac_output_length_bits,
2953            canonical_signed_info,
2954        ))
2955    })
2956}
2957
2958const SECOND_EDITION_GENERATION_C14N_URI: &str = "http://www.w3.org/2006/12/xml-c14n11";
2959
2960struct SourceEdit {
2961    range: Range<usize>,
2962    replacement: String,
2963}
2964
2965fn materialize_second_edition_c14n11_candidate(
2966    document: &XmlDocument,
2967    target_signature: usize,
2968    policy: &crate::policy::SigningPolicy,
2969    has_external_resources: bool,
2970) -> Result<Option<String>, SigningDigestError> {
2971    let edits = document.with_view(|view| {
2972        let signature = find_signing_signature_node(
2973            view.document(),
2974            SigningSignatureTarget::Index(target_signature),
2975        )?;
2976        let signed_info = find_required_child(signature, "SignedInfo")?;
2977        let mut reference_nodes = element_children(signed_info)
2978            .filter(|node| node.has_tag_name((XMLDSIG_NS, "Reference")))
2979            .collect::<Vec<_>>();
2980        if policy.manifest_processing == crate::policy::ManifestProcessing::Process {
2981            let reference_limit = policy
2982                .resources
2983                .max_references
2984                .min(MAX_REFERENCES_PER_SIGNATURE);
2985            for manifest in signature
2986                .children()
2987                .filter(|node| node.has_tag_name((XMLDSIG_NS, "Object")))
2988                .flat_map(|object| {
2989                    object
2990                        .children()
2991                        .filter(|node| node.has_tag_name((XMLDSIG_NS, "Manifest")))
2992                })
2993            {
2994                let mut manifest_references = 0usize;
2995                for child in element_children(manifest) {
2996                    verify_ds_element(child, "Reference")?;
2997                    if reference_nodes.len() == reference_limit {
2998                        return Err(crate::policy::PolicyViolation::ResourceLimit {
2999                            resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
3000                            maximum: reference_limit,
3001                            actual: reference_limit.saturating_add(1),
3002                        }
3003                        .into());
3004                    }
3005                    reference_nodes.push(child);
3006                    manifest_references += 1;
3007                }
3008                if manifest_references == 0 {
3009                    return Err(SigningDigestError::MissingElement {
3010                        element: "Reference",
3011                    });
3012                }
3013            }
3014        }
3015        if reference_nodes.len() > policy.resources.max_references {
3016            return Err(crate::policy::PolicyViolation::ResourceLimit {
3017                resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
3018                maximum: policy.resources.max_references,
3019                actual: reference_nodes.len(),
3020            }
3021            .into());
3022        }
3023
3024        let mut edits = Vec::new();
3025        for reference_node in reference_nodes {
3026            let structure = parse_signing_reference_structure(reference_node)?;
3027            validate_signing_reference_uri(structure.uri, policy)?;
3028            validate_signing_reference_request(structure.uri, has_external_resources)?;
3029            let digest_uri = required_algorithm_attr(structure.digest_method_node, "DigestMethod")?;
3030            let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
3031                SigningDigestError::UnsupportedAlgorithm {
3032                    uri: digest_uri.to_owned(),
3033                }
3034            })?;
3035            policy.check_digest_algorithm(digest_method)?;
3036
3037            let transform_uris = generation_transform_uris(structure.transforms_node)?;
3038            if transform_uris.len() > policy.resources.max_transforms_per_reference {
3039                return Err(crate::policy::PolicyViolation::ResourceLimit {
3040                    resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3041                    maximum: policy.resources.max_transforms_per_reference,
3042                    actual: transform_uris.len(),
3043                }
3044                .into());
3045            }
3046            if let Some(allowed) = policy.transforms.allowed_algorithms.as_ref()
3047                && let Some(disallowed) = transform_uris
3048                    .iter()
3049                    .find(|algorithm| !allowed.contains(**algorithm))
3050            {
3051                return Err(crate::policy::PolicyViolation::Algorithm {
3052                    operation: "signing transform",
3053                    algorithm: (*disallowed).to_owned(),
3054                }
3055                .into());
3056            }
3057            let initial_binary = !structure.uri.is_empty() && !structure.uri.starts_with('#');
3058            if generation_chain_produces_binary(initial_binary, &transform_uris) {
3059                continue;
3060            }
3061            if transform_uris.len() >= policy.resources.max_transforms_per_reference {
3062                return Err(crate::policy::PolicyViolation::ResourceLimit {
3063                    resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3064                    maximum: policy.resources.max_transforms_per_reference,
3065                    actual: transform_uris.len().saturating_add(1),
3066                }
3067                .into());
3068            }
3069            if policy
3070                .transforms
3071                .allowed_algorithms
3072                .as_ref()
3073                .is_some_and(|allowed| !allowed.contains(SECOND_EDITION_GENERATION_C14N_URI))
3074            {
3075                return Err(crate::policy::PolicyViolation::Algorithm {
3076                    operation: "signing transform",
3077                    algorithm: SECOND_EDITION_GENERATION_C14N_URI.to_owned(),
3078                }
3079                .into());
3080            }
3081
3082            let edit = if let Some(transforms_node) = structure.transforms_node {
3083                c14n11_edit_for_transforms(view.xml(), transforms_node)?
3084            } else {
3085                let prefix = reference_node.prefix().unwrap_or_default();
3086                let qualifier = xml_qualifier(prefix);
3087                SourceEdit {
3088                    range: structure.digest_method_node.range().start
3089                        ..structure.digest_method_node.range().start,
3090                    replacement: format!(
3091                        "<{qualifier}Transforms><{qualifier}Transform Algorithm=\"{SECOND_EDITION_GENERATION_C14N_URI}\"/></{qualifier}Transforms>"
3092                    ),
3093                }
3094            };
3095            edits.push(edit);
3096        }
3097        Ok::<_, SigningDigestError>(edits)
3098    })?;
3099    if edits.is_empty() {
3100        return Ok(None);
3101    }
3102
3103    let projected_len = edits
3104        .iter()
3105        .try_fold(document.as_xml().len(), |total, edit| {
3106            total
3107                .checked_sub(edit.range.len())
3108                .and_then(|value| value.checked_add(edit.replacement.len()))
3109                .ok_or_else(|| {
3110                    SigningDigestError::InvalidStructure("document length overflow".into())
3111                })
3112        })?;
3113    policy
3114        .resources
3115        .validate_xml_document_len(projected_len)
3116        .map_err(SigningDigestError::Policy)?;
3117
3118    let mut candidate = document.as_xml().to_owned();
3119    let mut edits = edits;
3120    edits.sort_unstable_by_key(|edit| std::cmp::Reverse(edit.range.start));
3121    for edit in edits {
3122        candidate.replace_range(edit.range, &edit.replacement);
3123    }
3124    Ok(Some(candidate))
3125}
3126
3127fn c14n11_edit_for_transforms(
3128    xml: &str,
3129    transforms_node: Node<'_, '_>,
3130) -> Result<SourceEdit, SigningDigestError> {
3131    let range = transforms_node.range();
3132    let source = xml.get(range.clone()).ok_or_else(|| {
3133        SigningDigestError::InvalidStructure("Transforms source range is unavailable".into())
3134    })?;
3135    let prefix = transforms_node.prefix().unwrap_or_default();
3136    let qualifier = xml_qualifier(prefix);
3137    let transform =
3138        format!("<{qualifier}Transform Algorithm=\"{SECOND_EDITION_GENERATION_C14N_URI}\"/>");
3139    if source.trim_end().ends_with("/>") {
3140        let trimmed = source.trim_end();
3141        let opening = trimmed.strip_suffix("/>").ok_or_else(|| {
3142            SigningDigestError::InvalidStructure(
3143                "self-closing Transforms source is unavailable".into(),
3144            )
3145        })?;
3146        let trailing = &source[trimmed.len()..];
3147        return Ok(SourceEdit {
3148            range,
3149            replacement: format!("{opening}>{transform}</{qualifier}Transforms>{trailing}"),
3150        });
3151    }
3152    let closing = source.rfind("</").ok_or_else(|| {
3153        SigningDigestError::InvalidStructure("Transforms closing tag is unavailable".into())
3154    })?;
3155    let offset = range.start + closing;
3156    Ok(SourceEdit {
3157        range: offset..offset,
3158        replacement: transform,
3159    })
3160}
3161
3162fn generation_transform_uris<'a>(
3163    transforms_node: Option<Node<'a, 'a>>,
3164) -> Result<Vec<&'a str>, SigningDigestError> {
3165    let Some(transforms_node) = transforms_node else {
3166        return Ok(Vec::new());
3167    };
3168    let mut algorithms = Vec::new();
3169    for child in element_children(transforms_node) {
3170        if algorithms.len() == MAX_TRANSFORMS_PER_REFERENCE {
3171            return Err(crate::policy::PolicyViolation::ResourceLimit {
3172                resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3173                maximum: MAX_TRANSFORMS_PER_REFERENCE,
3174                actual: MAX_TRANSFORMS_PER_REFERENCE.saturating_add(1),
3175            }
3176            .into());
3177        }
3178        if !child.has_tag_name((XMLDSIG_NS, "Transform")) {
3179            return Err(TransformError::UnsupportedTransform(
3180                "unexpected child element of <ds:Transforms>; only <ds:Transform> is allowed"
3181                    .into(),
3182            )
3183            .into());
3184        }
3185        let algorithm = child.attribute("Algorithm").ok_or_else(|| {
3186            TransformError::UnsupportedTransform(
3187                "missing Algorithm attribute on <Transform>".into(),
3188            )
3189        })?;
3190        if algorithm != ENVELOPED_SIGNATURE_URI
3191            && algorithm != BASE64_TRANSFORM_URI
3192            && algorithm != XPATH_TRANSFORM_URI
3193            && algorithm != XPATH_FILTER2_TRANSFORM_URI
3194            && crate::c14n::C14nAlgorithm::from_uri(algorithm).is_none()
3195        {
3196            return Err(TransformError::UnsupportedTransform(algorithm.to_owned()).into());
3197        }
3198        algorithms.push(algorithm);
3199    }
3200    Ok(algorithms)
3201}
3202
3203fn generation_chain_produces_binary(initial_binary: bool, algorithms: &[&str]) -> bool {
3204    algorithms.last().map_or(initial_binary, |algorithm| {
3205        *algorithm == BASE64_TRANSFORM_URI
3206            || crate::c14n::C14nAlgorithm::from_uri(algorithm).is_some()
3207    })
3208}
3209
3210fn xml_qualifier(prefix: &str) -> String {
3211    if prefix.is_empty() {
3212        String::new()
3213    } else {
3214        format!("{prefix}:")
3215    }
3216}
3217
3218fn parse_signing_document<'a>(
3219    xml: &'a str,
3220    policy: Option<&crate::policy::SigningPolicy>,
3221    budget: &XmlParseWorkBudget,
3222    backend: crate::XmlBackend,
3223) -> Result<Document<'a>, SigningDigestError> {
3224    let settings = policy
3225        .map(|policy| DocumentParseSettings::from_policy(&policy.xml, &policy.resources))
3226        .unwrap_or_default()
3227        .with_backend(backend);
3228    super::mutation::parse_with_options_and_budget(xml, settings, Some(budget)).map_err(|error| {
3229        match error.into_policy_violation(settings) {
3230            Ok(error) => SigningDigestError::Policy(error),
3231            Err(XmlDocumentError::Parse(error)) => SigningDigestError::XmlParse(error),
3232            Err(error) => SigningDigestError::Document(error),
3233        }
3234    })
3235}
3236
3237fn parse_private_key_pem(private_key_pem: &str) -> Result<Zeroizing<Vec<u8>>, SigningKeyError> {
3238    let (rest, pem) = x509_parser::pem::parse_x509_pem(private_key_pem.as_bytes())
3239        .map_err(|_| SigningKeyError::InvalidKeyPem)?;
3240    if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
3241        return Err(SigningKeyError::InvalidKeyPem);
3242    }
3243    if pem.label != "PRIVATE KEY" {
3244        return Err(SigningKeyError::InvalidKeyFormat { label: pem.label });
3245    }
3246    Ok(Zeroizing::new(pem.contents))
3247}
3248
3249enum SigningSignatureTarget {
3250    First,
3251    Last,
3252    Index(usize),
3253}
3254
3255fn find_signing_signature_node<'a>(
3256    doc: &'a Document<'a>,
3257    target: SigningSignatureTarget,
3258) -> Result<Node<'a, 'a>, SigningDigestError> {
3259    let mut signatures = doc.descendants().filter(|node| {
3260        node.is_element()
3261            && node.tag_name().name() == "Signature"
3262            && node.tag_name().namespace() == Some(XMLDSIG_NS)
3263    });
3264    match target {
3265        SigningSignatureTarget::First => signatures.next(),
3266        SigningSignatureTarget::Last => signatures.next_back(),
3267        SigningSignatureTarget::Index(index) => signatures.nth(index),
3268    }
3269    .ok_or(SigningDigestError::MissingElement {
3270        element: "Signature",
3271    })
3272}
3273
3274fn signing_signature_index(
3275    doc: &Document<'_>,
3276    start_node_id: Option<&str>,
3277    id_attributes: &[crate::IdAttributeRegistration],
3278    selection: SignatureTemplateSelection,
3279) -> Result<usize, SigningDigestError> {
3280    let selected = if let Some(id) = start_node_id {
3281        let start = signing_start_node(doc, id, id_attributes)?;
3282        let mut signatures = start
3283            .descendants()
3284            .filter(|node| node.has_tag_name((XMLDSIG_NS, "Signature")));
3285        match selection.target() {
3286            SigningSignatureTarget::First => signatures.next(),
3287            SigningSignatureTarget::Last => signatures.next_back(),
3288            SigningSignatureTarget::Index(_) => unreachable!("public selection is not indexed"),
3289        }
3290        .ok_or_else(|| {
3291            SigningDigestError::InvalidStructure(format!(
3292                "selected node subtree has no Signature: {id}"
3293            ))
3294        })?
3295    } else {
3296        find_signing_signature_node(doc, selection.target())?
3297    };
3298    signature_index(doc, selected)
3299}
3300
3301fn signing_start_node<'a>(
3302    doc: &'a Document<'a>,
3303    id: &str,
3304    id_attributes: &[crate::IdAttributeRegistration],
3305) -> Result<Node<'a, 'a>, SigningDigestError> {
3306    UriReferenceResolver::with_id_registrations(doc, id_attributes)
3307        .node_for_id(id)
3308        .ok_or_else(|| {
3309            SigningDigestError::InvalidStructure(format!(
3310                "selected node ID is missing or ambiguous: {id}"
3311            ))
3312        })
3313}
3314
3315fn signature_index(
3316    doc: &Document<'_>,
3317    selected: Node<'_, '_>,
3318) -> Result<usize, SigningDigestError> {
3319    doc.descendants()
3320        .filter(|node| node.has_tag_name((XMLDSIG_NS, "Signature")))
3321        .position(|node| node == selected)
3322        .ok_or(SigningDigestError::MissingElement {
3323            element: "Signature",
3324        })
3325}
3326
3327fn parse_signing_references(
3328    signed_info: Node<'_, '_>,
3329) -> Result<Vec<SigningReference>, SigningDigestError> {
3330    parse_signing_references_with_budget(signed_info, &mut XPathSignatureParseBudget::default())
3331}
3332
3333fn parse_signing_references_with_budget(
3334    signed_info: Node<'_, '_>,
3335    xpath_budget: &mut XPathSignatureParseBudget,
3336) -> Result<Vec<SigningReference>, SigningDigestError> {
3337    verify_ds_element(signed_info, "SignedInfo")?;
3338    let mut children = element_children(signed_info);
3339
3340    let c14n_node = children.next().ok_or(SigningDigestError::MissingElement {
3341        element: "CanonicalizationMethod",
3342    })?;
3343    verify_ds_element(c14n_node, "CanonicalizationMethod")?;
3344    required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
3345
3346    let signature_method_node = children.next().ok_or(SigningDigestError::MissingElement {
3347        element: "SignatureMethod",
3348    })?;
3349    verify_ds_element(signature_method_node, "SignatureMethod")?;
3350    required_algorithm_attr(signature_method_node, "SignatureMethod")?;
3351
3352    let mut references = Vec::new();
3353    for child in children {
3354        verify_ds_element(child, "Reference")?;
3355        if references.len() == MAX_REFERENCES_PER_SIGNATURE {
3356            return Err(SigningDigestError::InvalidStructure(format!(
3357                "SignedInfo contains more than {MAX_REFERENCES_PER_SIGNATURE} Reference elements"
3358            )));
3359        }
3360        references.push(parse_signing_reference(child, xpath_budget)?);
3361    }
3362    if references.is_empty() {
3363        return Err(SigningDigestError::MissingElement {
3364            element: "Reference",
3365        });
3366    }
3367    Ok(references)
3368}
3369
3370fn parse_signing_manifest_references(
3371    signature: Node<'_, '_>,
3372    xpath_budget: &mut XPathSignatureParseBudget,
3373    mut remaining_capacity: usize,
3374    maximum_references: usize,
3375) -> Result<Vec<SigningReference>, SigningDigestError> {
3376    let mut references = Vec::new();
3377    for manifest in signature
3378        .children()
3379        .filter(|node| node.has_tag_name((XMLDSIG_NS, "Object")))
3380        .flat_map(|object| {
3381            object
3382                .children()
3383                .filter(|node| node.has_tag_name((XMLDSIG_NS, "Manifest")))
3384        })
3385    {
3386        let mut manifest_references = 0usize;
3387        for child in element_children(manifest) {
3388            verify_ds_element(child, "Reference")?;
3389            if remaining_capacity == 0 {
3390                return Err(crate::policy::PolicyViolation::ResourceLimit {
3391                    resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
3392                    maximum: maximum_references,
3393                    actual: maximum_references.saturating_add(1),
3394                }
3395                .into());
3396            }
3397            remaining_capacity -= 1;
3398            references.push(parse_signing_reference(child, xpath_budget)?);
3399            manifest_references += 1;
3400        }
3401        if manifest_references == 0 {
3402            return Err(SigningDigestError::MissingElement {
3403                element: "Reference",
3404            });
3405        }
3406    }
3407    Ok(references)
3408}
3409
3410fn parse_signing_reference(
3411    reference_node: Node<'_, '_>,
3412    xpath_budget: &mut XPathSignatureParseBudget,
3413) -> Result<SigningReference, SigningDigestError> {
3414    let structure = parse_signing_reference_structure(reference_node)?;
3415    let transforms = structure.transforms_node.map_or_else(
3416        || Ok(Vec::new()),
3417        |node| parse_transforms_with_budget(node, xpath_budget),
3418    )?;
3419    let digest_uri = required_algorithm_attr(structure.digest_method_node, "DigestMethod")?;
3420    let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
3421        SigningDigestError::UnsupportedAlgorithm {
3422            uri: digest_uri.to_string(),
3423        }
3424    })?;
3425
3426    Ok(SigningReference {
3427        uri: structure.uri.to_owned(),
3428        origin_node_id: reference_node.id(),
3429        transforms,
3430        digest_method,
3431        digest_value_range: structure.digest_value_node.range(),
3432        digest_value_node_id: structure.digest_value_node.id(),
3433    })
3434}
3435
3436struct SigningReferenceStructure<'a> {
3437    uri: &'a str,
3438    transforms_node: Option<Node<'a, 'a>>,
3439    digest_method_node: Node<'a, 'a>,
3440    digest_value_node: Node<'a, 'a>,
3441}
3442
3443fn parse_signing_reference_structure<'a>(
3444    reference_node: Node<'a, 'a>,
3445) -> Result<SigningReferenceStructure<'a>, SigningDigestError> {
3446    let uri = reference_node.attribute("URI").ok_or_else(|| {
3447        SigningDigestError::InvalidStructure("signing Reference must include URI attribute".into())
3448    })?;
3449    let mut children = element_children(reference_node);
3450    let first = children.next().ok_or(SigningDigestError::MissingElement {
3451        element: "DigestMethod",
3452    })?;
3453    let (transforms_node, digest_method_node) = if first.has_tag_name((XMLDSIG_NS, "Transforms")) {
3454        (
3455            Some(first),
3456            children.next().ok_or(SigningDigestError::MissingElement {
3457                element: "DigestMethod",
3458            })?,
3459        )
3460    } else {
3461        (None, first)
3462    };
3463    verify_ds_element(digest_method_node, "DigestMethod")?;
3464    let digest_value_node = children.next().ok_or(SigningDigestError::MissingElement {
3465        element: "DigestValue",
3466    })?;
3467    verify_ds_element(digest_value_node, "DigestValue")?;
3468    if let Some(unexpected) = children.next() {
3469        return Err(SigningDigestError::InvalidStructure(format!(
3470            "unexpected element <{}> after <DigestValue> in <Reference>",
3471            unexpected.tag_name().name()
3472        )));
3473    }
3474    Ok(SigningReferenceStructure {
3475        uri,
3476        transforms_node,
3477        digest_method_node,
3478        digest_value_node,
3479    })
3480}
3481
3482fn find_required_child<'a>(
3483    parent: Node<'a, 'a>,
3484    child_name: &'static str,
3485) -> Result<Node<'a, 'a>, SigningDigestError> {
3486    parent
3487        .children()
3488        .find(|node| {
3489            node.is_element()
3490                && node.tag_name().name() == child_name
3491                && node.tag_name().namespace() == Some(XMLDSIG_NS)
3492        })
3493        .ok_or(SigningDigestError::MissingElement {
3494            element: child_name,
3495        })
3496}
3497
3498fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
3499    node.children().filter(Node::is_element)
3500}
3501
3502fn verify_ds_element(
3503    node: Node<'_, '_>,
3504    expected_name: &'static str,
3505) -> Result<(), SigningDigestError> {
3506    if !node.is_element() {
3507        return Err(SigningDigestError::InvalidStructure(format!(
3508            "expected element <{expected_name}>, got non-element node"
3509        )));
3510    }
3511    let tag = node.tag_name();
3512    if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
3513        return Err(SigningDigestError::InvalidStructure(format!(
3514            "expected <ds:{expected_name}>, got <{}>",
3515            tag.name()
3516        )));
3517    }
3518    Ok(())
3519}
3520
3521fn required_algorithm_attr<'a>(
3522    node: Node<'a, 'a>,
3523    element_name: &'static str,
3524) -> Result<&'a str, SigningDigestError> {
3525    node.attribute("Algorithm").ok_or_else(|| {
3526        SigningDigestError::InvalidStructure(format!(
3527            "missing Algorithm attribute on <{element_name}>"
3528        ))
3529    })
3530}
3531
3532#[cfg(test)]
3533mod error_conversion_tests {
3534    use super::*;
3535    use crate::policy::PolicyViolation;
3536
3537    struct RejectingSigningKey;
3538
3539    impl SigningKey for RejectingSigningKey {
3540        fn sign(
3541            &self,
3542            _algorithm: SignatureAlgorithm,
3543            _canonical_signed_info: &[u8],
3544        ) -> Result<Vec<u8>, SigningKeyError> {
3545            Err(SigningKeyError::SigningFailed)
3546        }
3547
3548        fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
3549            Err(SigningKeyError::PublicKeyEncodingFailed)
3550        }
3551    }
3552
3553    struct FixedRsaSigningKey;
3554
3555    impl SigningKey for FixedRsaSigningKey {
3556        fn sign(
3557            &self,
3558            _algorithm: SignatureAlgorithm,
3559            _canonical_signed_info: &[u8],
3560        ) -> Result<Vec<u8>, SigningKeyError> {
3561            Ok(vec![0x5a; 256])
3562        }
3563
3564        fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
3565            Ok(SigningPublicKeyInfo::Rsa {
3566                spki_der: Vec::new(),
3567                modulus: vec![0x80; 256],
3568                exponent: vec![1, 0, 1],
3569            })
3570        }
3571    }
3572
3573    #[test]
3574    fn namespace_exhaustion_preserves_typed_signing_errors() {
3575        // Template and builder conversions share the same typed namespace-budget refusal.
3576        let make_error = || {
3577            XmlDocumentError::Parse(crate::xml::dom::ParseError::NamespaceBindingLimitReached {
3578                maximum: 2,
3579                actual: 3,
3580            })
3581        };
3582        assert!(matches!(
3583            owned_document_policy_violation(make_error()),
3584            Ok(PolicyViolation::ResourceLimit {
3585                resource: crate::policy::resource_name::XML_NAMESPACE_BINDINGS,
3586                maximum: 2,
3587                actual: 3,
3588            })
3589        ));
3590        let mut policy = crate::policy::SigningPolicy::default();
3591        policy.resources.max_xml_namespace_bindings = 0;
3592        let context = SignContext::new(&RejectingSigningKey).policy(policy);
3593        let builder = SignatureBuilder::new(
3594            crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3595            SignatureAlgorithm::RsaSha256,
3596        );
3597        let xml = "<root xmlns:p='urn:p'/>";
3598        for result in [
3599            context.sign_template(xml),
3600            context.sign_with_builder(xml, &builder),
3601        ] {
3602            assert!(
3603                matches!(
3604                    result,
3605                    Err(SigningError::Policy(PolicyViolation::ResourceLimit {
3606                        resource: crate::policy::resource_name::XML_NAMESPACE_BINDINGS,
3607                        maximum: 0,
3608                        actual: 1,
3609                    }))
3610                ),
3611                "unexpected signing result: {result:?}"
3612            );
3613        }
3614    }
3615
3616    #[test]
3617    fn signing_error_promotes_every_policy_failure() {
3618        // All policy refusals use one public pipeline variant regardless of
3619        // which internal signing stage first enforces the immutable snapshot.
3620        let digest = SigningError::from(SigningDigestError::Policy(PolicyViolation::Algorithm {
3621            operation: "signing",
3622            algorithm: "urn:test:digest".into(),
3623        }));
3624        assert!(matches!(digest, SigningError::Policy(_)));
3625
3626        let mutation = SigningError::from(SigningDigestError::XmlMutation(
3627            XmlMutationError::Policy(PolicyViolation::ResourceLimit {
3628                resource: crate::policy::resource_name::XML_DOCUMENT,
3629                maximum: 1,
3630                actual: 2,
3631            }),
3632        ));
3633        assert!(matches!(mutation, SigningError::Policy(_)));
3634    }
3635
3636    #[test]
3637    fn manifest_reparse_consumes_the_signature_wide_xpath_budget() {
3638        // Signing reparses Manifest references after each dependency level.
3639        // Repeated parser/compiler work must consume the original signature
3640        // budget rather than receiving a fresh allowance for every level.
3641        let filter = r#"<xf:XPath xmlns:xf="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</xf:XPath>"#;
3642        let signed_info_transforms = format!(
3643            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>"#,
3644            filter.repeat(64)
3645        );
3646        let signed_info_references = (0..62)
3647            .map(|index| {
3648                let extra = if index == 0 {
3649                    r#"<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>true()</ds:XPath></ds:Transform>"#
3650                } else {
3651                    ""
3652                };
3653                format!(
3654                    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>"##
3655                )
3656            })
3657            .collect::<String>();
3658        let manifest_reference = |id: &str| {
3659            format!(
3660                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>"##
3661            )
3662        };
3663        let xml = format!(
3664            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>"##,
3665            manifest_reference("payload"),
3666            manifest_reference("payload")
3667        );
3668        let policy = crate::policy::SigningPolicy {
3669            manifest_processing: crate::policy::ManifestProcessing::Process,
3670            ..crate::policy::SigningPolicy::default()
3671        };
3672
3673        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3674        let error = fill_reference_digest_values_in_dependency_order(
3675            &mut document,
3676            TransformOptions::default(),
3677            &policy,
3678            crate::provider::default_provider(),
3679            &mut SigningOperationBudgets::default(),
3680            0,
3681            &[],
3682        )
3683        .expect_err("Manifest reparse must not reset the XPath parse budget");
3684
3685        assert!(
3686            matches!(
3687                &error,
3688                SigningDigestError::Transform(TransformError::Policy(
3689                    crate::policy::PolicyViolation::ResourceLimit {
3690                        resource: "XPath expressions",
3691                        ..
3692                    }
3693                ))
3694            ),
3695            "expected the shared XPath budget error, got: {error:?}"
3696        );
3697    }
3698
3699    #[test]
3700    fn dependency_levels_share_the_xml_parse_work_budget() {
3701        // Nested Manifest dependencies require successive digest generations.
3702        // Analysis mutations and every level's validation/commit parses must
3703        // consume one monotonic budget instead of resetting in recursive work.
3704        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>"##;
3705        let policy = crate::policy::SigningPolicy {
3706            manifest_processing: crate::policy::ManifestProcessing::Process,
3707            ..crate::policy::SigningPolicy::default()
3708        };
3709        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3710        let mut budgets = SigningOperationBudgets::from_resources(&policy.resources);
3711
3712        fill_reference_digest_values_in_dependency_order(
3713            &mut document,
3714            TransformOptions::default(),
3715            &policy,
3716            crate::provider::default_provider(),
3717            &mut budgets,
3718            0,
3719            &[],
3720        )
3721        .expect("the default cumulative budget must cover nested dependencies");
3722        let consumed = budgets.transforms.xml_parse_work().consumed();
3723        assert!(
3724            consumed > xml.len().saturating_mul(6),
3725            "analysis and dependency reparses must all be charged"
3726        );
3727
3728        let mut constrained_policy = policy;
3729        constrained_policy.resources.max_xml_parse_work_bytes = consumed - 1;
3730        let mut constrained_document = XmlDocument::parse(xml).expect("fixture must parse");
3731        let mut constrained_budgets =
3732            SigningOperationBudgets::from_resources(&constrained_policy.resources);
3733        let error = fill_reference_digest_values_in_dependency_order(
3734            &mut constrained_document,
3735            TransformOptions::default(),
3736            &constrained_policy,
3737            crate::provider::default_provider(),
3738            &mut constrained_budgets,
3739            0,
3740            &[],
3741        )
3742        .expect_err("one byte below measured work must fail closed");
3743
3744        assert!(matches!(
3745            error,
3746            SigningDigestError::Policy(PolicyViolation::ResourceLimit {
3747                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3748                maximum,
3749                actual,
3750            }) if maximum == consumed - 1 && actual >= consumed
3751        ));
3752    }
3753
3754    #[test]
3755    fn final_signed_info_parse_consumes_the_signing_xpath_budget() {
3756        // One XPath Reference is parsed while discovering references, while
3757        // analyzing dependencies, and while filling its dependency level. The
3758        // final SignedInfo parse must consume the same operation-wide budget.
3759        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>"##;
3760        let mut policy = crate::policy::SigningPolicy::default();
3761        policy.resources.max_xpath_expressions = 3;
3762
3763        let error = SignContext::new(&RejectingSigningKey)
3764            .policy(policy)
3765            .sign_template(xml)
3766            .expect_err("the final SignedInfo parse must not reset the XPath budget");
3767
3768        assert!(
3769            matches!(
3770                error,
3771                SigningError::Policy(PolicyViolation::ResourceLimit {
3772                    resource: crate::policy::resource_name::XPATH_EXPRESSIONS,
3773                    maximum: 3,
3774                    ..
3775                })
3776            ),
3777            "expected the shared XPath parse budget error, got: {error:?}"
3778        );
3779    }
3780
3781    #[test]
3782    fn signing_initial_parse_consumes_the_operation_xml_parse_budget() {
3783        // The input parse and every later retained-document reparse belong to
3784        // one monotonic operation allowance; helpers must not create a fresh
3785        // budget before digest or mutation work begins.
3786        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>"##;
3787        let mut policy = crate::policy::SigningPolicy::default();
3788        policy.resources.max_xml_parse_work_bytes = 0;
3789
3790        let error = SignContext::new(&RejectingSigningKey)
3791            .policy(policy)
3792            .sign_template(xml)
3793            .expect_err("a zero parse-work budget must reject the initial parse");
3794
3795        assert!(matches!(
3796            error,
3797            SigningError::Policy(PolicyViolation::ResourceLimit {
3798                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3799                maximum: 0,
3800                actual,
3801            }) if actual == xml.len()
3802        ));
3803    }
3804
3805    #[test]
3806    fn signing_string_entry_point_enforces_policy_depth() {
3807        // Operation parsing must use the compiled policy depth rather than the
3808        // process-wide hard ceiling, before template discovery or key use.
3809        let mut policy = crate::policy::SigningPolicy::default();
3810        policy.resources.max_xml_depth = 2;
3811        let xml = "<root><child><leaf/></child></root>";
3812
3813        assert!(matches!(
3814            SignContext::new(&RejectingSigningKey)
3815                .policy(policy)
3816                .sign_template(xml),
3817            Err(SigningError::Policy(PolicyViolation::ResourceLimit {
3818                resource: crate::policy::resource_name::XML_DEPTH,
3819                maximum: 2,
3820                actual: 3,
3821            }))
3822        ));
3823    }
3824
3825    #[test]
3826    fn builder_append_reports_policy_depth() {
3827        // The generated template fits as a standalone tree, but its methods
3828        // cross the same depth policy after the Signature is appended.
3829        let mut policy = crate::policy::SigningPolicy::default();
3830        policy.resources.max_xml_depth = 4;
3831        let builder = SignatureBuilder::new(
3832            crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3833            SignatureAlgorithm::RsaSha256,
3834        )
3835        .add_reference(crate::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256).uri(""));
3836
3837        let result = SignContext::new(&FixedRsaSigningKey)
3838            .policy(policy)
3839            .sign_with_builder("<root/>", &builder);
3840        assert!(
3841            matches!(
3842                result,
3843                Err(SigningError::Policy(PolicyViolation::ResourceLimit {
3844                    resource: crate::policy::resource_name::XML_DEPTH,
3845                    maximum: 4,
3846                    actual: 5,
3847                }))
3848            ),
3849            "unexpected builder depth result: {result:?}"
3850        );
3851    }
3852
3853    #[test]
3854    fn owned_signing_staged_copies_preserve_policy_errors() {
3855        // Both retained-document entry points must expose operation policy
3856        // exhaustion directly and leave the caller's generation untouched.
3857        let mut policy = crate::policy::SigningPolicy::default();
3858        policy.resources.max_xml_parse_work_bytes = 0;
3859        let context = SignContext::new(&RejectingSigningKey).policy(policy);
3860
3861        let mut template_document = XmlDocument::parse("<root/>").expect("fixture must parse");
3862        let template_before = template_document.as_xml().to_owned();
3863        let error = context
3864            .sign_document(&mut template_document)
3865            .expect_err("the staged template copy must exhaust the operation budget");
3866        assert!(matches!(
3867            error,
3868            SigningError::Policy(PolicyViolation::ResourceLimit {
3869                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3870                maximum: 0,
3871                actual,
3872            }) if actual == template_before.len()
3873        ));
3874        assert_eq!(template_document.as_xml(), template_before);
3875        assert_eq!(template_document.generation(), 0);
3876
3877        let mut builder_document = XmlDocument::parse("<root/>").expect("fixture must parse");
3878        let builder_before = builder_document.as_xml().to_owned();
3879        let builder = SignatureBuilder::new(
3880            crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3881            SignatureAlgorithm::RsaSha256,
3882        );
3883        let error = context
3884            .sign_document_with_builder(&mut builder_document, &builder)
3885            .expect_err("the staged builder copy must exhaust the operation budget");
3886        assert!(matches!(
3887            error,
3888            SigningError::Policy(PolicyViolation::ResourceLimit {
3889                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3890                maximum: 0,
3891                actual,
3892            }) if actual == builder_before.len()
3893        ));
3894        assert_eq!(builder_document.as_xml(), builder_before);
3895        assert_eq!(builder_document.generation(), 0);
3896    }
3897
3898    #[test]
3899    fn owned_signing_commits_the_validated_stage_without_reparsing() {
3900        // Atomic commit must adopt the already validated staged cell. Charging
3901        // another complete backend parse makes valid maximum-size inputs exceed
3902        // the operation ceiling only because they use the owned entry point.
3903        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>"##;
3904        let policy = crate::policy::SigningPolicy::default();
3905        let context = SignContext::new(&FixedRsaSigningKey).policy(policy.clone());
3906        let source = XmlDocument::parse(xml).expect("fixture must parse");
3907        let mut measured = SigningOperationBudgets::from_resources(&policy.resources);
3908        let mut staged = source
3909            .staged_copy_with_budget(
3910                DocumentParseSettings::from_policy(&policy.xml, &policy.resources),
3911                measured.transforms.xml_parse_work(),
3912            )
3913            .expect("staging must parse");
3914        context
3915            .sign_document_in_place(&mut staged, &mut measured)
3916            .expect("staged signing must succeed");
3917        let exact_stage_work = measured.transforms.xml_parse_work().consumed();
3918
3919        let mut constrained_policy = policy;
3920        constrained_policy.resources.max_xml_parse_work_bytes = exact_stage_work;
3921        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3922        SignContext::new(&FixedRsaSigningKey)
3923            .policy(constrained_policy)
3924            .sign_document(&mut document)
3925            .expect("commit must not parse the validated stage again");
3926
3927        assert_eq!(document.generation(), 1);
3928        assert!(!document.as_xml().contains("<ds:DigestValue/>"));
3929        assert!(!document.as_xml().contains("<ds:SignatureValue/>"));
3930    }
3931
3932    #[test]
3933    fn builder_signing_fits_the_document_to_parse_work_ratio() {
3934        // A builder operation at the configured document ceiling must fit the
3935        // implementation's hard allowance. Generated base64 text and the
3936        // appended generated template need one committed candidate parse each,
3937        // not an untrusted-fragment validation parse plus commit. Differential
3938        // builds meter their comparison backend without reducing this envelope.
3939        let padding = "x".repeat(64 * 1024);
3940        let xml = format!("<root><payload Id=\"payload\"/><padding>{padding}</padding></root>");
3941        let builder = SignatureBuilder::new(
3942            crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3943            SignatureAlgorithm::RsaSha256,
3944        )
3945        .add_reference(
3946            crate::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256).uri("#payload"),
3947        );
3948        let maximum_document_bytes = xml.len() + 4 * 1024;
3949        let mut policy = crate::policy::SigningPolicy::default();
3950        policy.resources.max_xml_document_bytes = maximum_document_bytes;
3951        policy.resources.max_xml_parse_work_bytes =
3952            maximum_document_bytes * crate::hard_limits::XML_PARSE_WORK_PASS_CEILING;
3953
3954        let mut measurement_policy = policy.clone();
3955        measurement_policy.resources.max_xml_parse_work_bytes =
3956            crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING;
3957        let measurement_context =
3958            SignContext::new(&FixedRsaSigningKey).policy(measurement_policy.clone());
3959        let mut measurement_budgets =
3960            SigningOperationBudgets::from_resources(&measurement_policy.resources);
3961        let mut measurement_document = XmlDocument::parse_with_settings_and_budget(
3962            xml.clone(),
3963            DocumentParseSettings::from_policy(
3964                &measurement_policy.xml,
3965                &measurement_policy.resources,
3966            ),
3967            measurement_budgets.transforms.xml_parse_work(),
3968        )
3969        .expect("measurement input must parse");
3970        measurement_context
3971            .sign_document_with_builder_in_place(
3972                &mut measurement_document,
3973                &builder,
3974                &mut measurement_budgets,
3975            )
3976            .expect("measurement signing must succeed");
3977        let consumed = measurement_budgets.transforms.xml_parse_work().consumed();
3978        assert!(
3979            consumed <= maximum_document_bytes * crate::hard_limits::XML_PARSE_WORK_PASS_CEILING,
3980            "builder signing consumed {consumed} bytes for a {maximum_document_bytes}-byte ceiling"
3981        );
3982
3983        let signed = SignContext::new(&FixedRsaSigningKey)
3984            .policy(policy.clone())
3985            .sign_with_builder(&xml, &builder)
3986            .expect("string builder signing must fit the advertised parse-work ratio");
3987        assert!(signed.contains("DigestValue>"));
3988        assert!(signed.contains("SignatureValue>"));
3989
3990        let mut owned = XmlDocument::parse(&xml).expect("fixture must parse");
3991        SignContext::new(&FixedRsaSigningKey)
3992            .policy(policy)
3993            .sign_document_with_builder(&mut owned, &builder)
3994            .expect("owned builder signing must fit the advertised parse-work ratio");
3995        assert!(owned.as_xml().contains("DigestValue>"));
3996        assert!(owned.as_xml().contains("SignatureValue>"));
3997    }
3998
3999    #[test]
4000    fn dtd_capable_staged_copy_charges_both_parser_passes() {
4001        // DTD-capable documents run a provenance parse before the retained
4002        // document parse. Both passes belong to the signing operation budget.
4003        let xml = "<root/>";
4004        let mut parsing_policy = crate::policy::SigningPolicy::default();
4005        parsing_policy.xml.allow_internal_dtd = true;
4006        let document = XmlDocument::parse_with_policy(xml, &parsing_policy)
4007            .expect("the fixture must retain DTD-capable parse settings");
4008
4009        parsing_policy.resources.max_xml_parse_work_bytes = xml.len();
4010        let budget = XmlParseWorkBudget::from_resources(&parsing_policy.resources);
4011        assert!(matches!(
4012            document.staged_copy_with_budget(DocumentParseSettings::default(), &budget),
4013            Err(XmlDocumentError::Policy(PolicyViolation::ResourceLimit {
4014                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
4015                maximum,
4016                actual,
4017            })) if maximum == xml.len() && actual == xml.len() * 2
4018        ));
4019    }
4020
4021    #[test]
4022    fn owned_signing_mappers_preserve_document_size_policy_errors() {
4023        // Template and digest mutations share one resource-error contract even
4024        // though their surrounding signing error types differ.
4025        let maximum = 8;
4026        let actual = 9;
4027        assert!(matches!(
4028            map_owned_document_mutation_error(XmlDocumentError::DocumentTooLarge {
4029                maximum,
4030                actual,
4031            }),
4032            SigningError::Policy(PolicyViolation::ResourceLimit {
4033                resource: crate::policy::resource_name::XML_DOCUMENT,
4034                maximum: 8,
4035                actual: 9,
4036            })
4037        ));
4038        assert!(matches!(
4039            map_owned_document_digest_mutation_error(XmlDocumentError::DocumentTooLarge {
4040                maximum,
4041                actual,
4042            }),
4043            SigningDigestError::Policy(PolicyViolation::ResourceLimit {
4044                resource: crate::policy::resource_name::XML_DOCUMENT,
4045                maximum: 8,
4046                actual: 9,
4047            })
4048        ));
4049    }
4050
4051    #[test]
4052    fn signing_rejects_xpath_control_dependencies_on_digest_values() {
4053        // The second Reference excludes DigestValue nodes from its output but
4054        // reads the first DigestValue to decide whether payload remains. Filling
4055        // both references in one level would therefore invalidate the result.
4056        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>"##;
4057
4058        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4059        let error = fill_reference_digest_values_in_dependency_order(
4060            &mut document,
4061            TransformOptions::default(),
4062            &crate::policy::SigningPolicy::default(),
4063            crate::provider::default_provider(),
4064            &mut SigningOperationBudgets::default(),
4065            0,
4066            &[],
4067        )
4068        .expect_err("mutable XPath control dependencies must fail closed");
4069
4070        assert!(
4071            matches!(
4072                &error,
4073                SigningDigestError::InvalidStructure(message)
4074                    if message.contains("dependency cycle")
4075            ),
4076            "expected a dependency-cycle rejection, got: {error:?}"
4077        );
4078    }
4079
4080    #[test]
4081    fn signing_allows_payload_local_xpath_value_predicates() {
4082        // Attribute comparisons read payload metadata, not mutable values in a
4083        // disjoint Signature subtree. They must not manufacture a self-cycle.
4084        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>"##;
4085
4086        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4087        fill_reference_digest_values_in_dependency_order(
4088            &mut document,
4089            TransformOptions::default(),
4090            &crate::policy::SigningPolicy::default(),
4091            crate::provider::default_provider(),
4092            &mut SigningOperationBudgets::default(),
4093            0,
4094            &[],
4095        )
4096        .expect("payload-local XPath predicates must not depend on Signature values");
4097
4098        assert_ne!(document.as_xml(), xml);
4099    }
4100
4101    #[test]
4102    fn signing_rejects_references_that_retain_signature_value() {
4103        // SignatureValue is filled after every Reference digest. A Reference
4104        // retaining that node is therefore an unavoidable signing cycle even
4105        // when all DigestValue nodes are excluded from the resulting node-set.
4106        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>"##;
4107
4108        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4109        let error = fill_reference_digest_values_in_dependency_order(
4110            &mut document,
4111            TransformOptions::default(),
4112            &crate::policy::SigningPolicy::default(),
4113            crate::provider::default_provider(),
4114            &mut SigningOperationBudgets::default(),
4115            0,
4116            &[],
4117        )
4118        .expect_err("a mutable SignatureValue dependency must fail before signing");
4119
4120        assert!(
4121            matches!(
4122                &error,
4123                SigningDigestError::InvalidStructure(message)
4124                    if message.contains("SignatureValue") && message.contains("cycle")
4125            ),
4126            "expected a SignatureValue dependency-cycle rejection, got: {error:?}"
4127        );
4128    }
4129
4130    #[test]
4131    fn second_edition_c14n_materialization_handles_both_reference_shapes() {
4132        // Templates may omit <Transforms> entirely or serialize an empty
4133        // prefixed element. Both forms must become one valid explicit chain.
4134        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/2006/12/xml-c14n11"/><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="#payload"><ds:Transforms/><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/></ds:Signature></root>"##;
4135        let document = XmlDocument::parse(xml).expect("fixture must parse");
4136        let candidate = materialize_second_edition_c14n11_candidate(
4137            &document,
4138            0,
4139            &crate::policy::SigningPolicy::default(),
4140            false,
4141        )
4142        .expect("materialization planning must succeed")
4143        .expect("both node-set chains require explicit canonicalization");
4144
4145        assert_eq!(
4146            candidate
4147                .matches(SECOND_EDITION_GENERATION_C14N_URI)
4148                .count(),
4149            3
4150        );
4151        assert!(candidate.contains("<ds:Transforms><ds:Transform"));
4152        XmlDocument::parse(candidate).expect("materialized candidate must remain well-formed");
4153    }
4154
4155    #[test]
4156    fn second_edition_c14n_materialization_is_idempotent_for_binary_chains() {
4157        // An explicit canonicalization already produces octets; planning must
4158        // avoid both duplicate transforms and a no-op document generation.
4159        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/2006/12/xml-c14n11"/><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/2006/12/xml-c14n11"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue/></ds:Reference></ds:SignedInfo><ds:SignatureValue/></ds:Signature></root>"##;
4160        let document = XmlDocument::parse(xml).expect("fixture must parse");
4161        let candidate = materialize_second_edition_c14n11_candidate(
4162            &document,
4163            0,
4164            &crate::policy::SigningPolicy::default(),
4165            false,
4166        )
4167        .expect("materialization planning must succeed");
4168
4169        assert!(candidate.is_none());
4170    }
4171}