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::DocumentTooLarge { maximum, actual } => {
1991            Ok(crate::policy::PolicyViolation::ResourceLimit {
1992                resource: crate::policy::resource_name::XML_DOCUMENT,
1993                maximum,
1994                actual,
1995            })
1996        }
1997        XmlDocumentError::DocumentTooDeep { maximum, actual } => {
1998            Ok(crate::policy::PolicyViolation::ResourceLimit {
1999                resource: crate::policy::resource_name::XML_DEPTH,
2000                maximum,
2001                actual,
2002            })
2003        }
2004        XmlDocumentError::ProjectedNodeLimit { maximum } => {
2005            Ok(crate::policy::PolicyViolation::ResourceLimit {
2006                resource: crate::policy::resource_name::XML_NODES,
2007                maximum,
2008                actual: maximum.saturating_add(1),
2009            })
2010        }
2011        error => Err(error),
2012    }
2013}
2014
2015fn encode_signature_output(
2016    algorithm: SignatureAlgorithm,
2017    signature: Vec<u8>,
2018    encoding: crate::policy::EcdsaSignatureValueEncoding,
2019) -> Result<Vec<u8>, SigningError> {
2020    if matches!(
2021        (algorithm, encoding),
2022        (
2023            SignatureAlgorithm::EcdsaSha1
2024                | SignatureAlgorithm::EcdsaSha224
2025                | SignatureAlgorithm::EcdsaSha256
2026                | SignatureAlgorithm::EcdsaSha384
2027                | SignatureAlgorithm::EcdsaSha512,
2028            crate::policy::EcdsaSignatureValueEncoding::XmlSecAsn1Der
2029        )
2030    ) {
2031        return encode_ecdsa_signature_as_der(&signature)
2032            .ok_or(SigningKeyError::InvalidPublicKeyInfo.into());
2033    }
2034    Ok(signature)
2035}
2036
2037#[derive(Debug, Clone)]
2038struct SigningReference {
2039    uri: String,
2040    origin_node_id: NodeId,
2041    transforms: Vec<Transform>,
2042    digest_method: DigestAlgorithm,
2043    digest_value_range: Range<usize>,
2044    digest_value_node_id: NodeId,
2045}
2046
2047struct SigningOperationBudgets {
2048    transforms: TransformExecutionBudget,
2049    xpath_parse: XPathSignatureParseBudget,
2050}
2051
2052struct SigningReferenceRequest<'a, 'resources> {
2053    target_signature: usize,
2054    id_attributes: &'a [crate::IdAttributeRegistration],
2055    external_resources: &'a ExternalResourceContext<'resources>,
2056}
2057
2058struct SigningPlanNodes {
2059    canonicalization: OperationNodeId,
2060    crypto: OperationNodeId,
2061    evidence: OperationNodeId,
2062    mutation: OperationNodeId,
2063}
2064
2065impl SigningOperationBudgets {
2066    fn from_resources(resources: &crate::policy::ResourcePolicy) -> Self {
2067        Self {
2068            transforms: TransformExecutionBudget::from_resources(resources),
2069            xpath_parse: XPathSignatureParseBudget::from_resources(resources),
2070        }
2071    }
2072
2073    fn from_resources_with_backend(
2074        resources: &crate::policy::ResourcePolicy,
2075        backend: crate::XmlBackend,
2076    ) -> Self {
2077        Self {
2078            transforms: TransformExecutionBudget::from_resources(resources)
2079                .with_xml_backend(backend),
2080            xpath_parse: XPathSignatureParseBudget::from_resources(resources),
2081        }
2082    }
2083}
2084
2085impl Default for SigningOperationBudgets {
2086    fn default() -> Self {
2087        Self::from_resources(&crate::policy::ResourcePolicy::default())
2088    }
2089}
2090
2091/// Compute base64 digest values for every `<Reference>` in the signing template.
2092///
2093/// References are processed in `<SignedInfo>` document order under the last
2094/// XMLDSig `<Signature>` element. `sign_with_builder()` appends a new template
2095/// at the end of the source root, so older signatures in an already-signed
2096/// document must not become the signing target.
2097pub fn compute_reference_digest_values(
2098    xml: &str,
2099) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2100    let execution_budget = TransformExecutionBudget::default();
2101    compute_reference_digest_values_with_options(
2102        xml,
2103        TransformOptions::default(),
2104        None,
2105        crate::provider::default_provider(),
2106        &execution_budget,
2107        None,
2108        &[],
2109    )
2110}
2111
2112fn compute_reference_digest_values_with_options(
2113    xml: &str,
2114    transform_options: TransformOptions,
2115    policy: Option<&crate::policy::SigningPolicy>,
2116    provider: &dyn crate::provider::CryptoProvider,
2117    execution_budget: &TransformExecutionBudget,
2118    target_signature: Option<usize>,
2119    id_attributes: &[crate::IdAttributeRegistration],
2120) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2121    let (prepared, target_signature) =
2122        prepare_reference_digest_input(xml, policy, execution_budget, target_signature)?;
2123    compute_prepared_reference_digest_values_with_options(
2124        prepared.as_ref(),
2125        transform_options,
2126        policy,
2127        provider,
2128        execution_budget,
2129        target_signature,
2130        id_attributes,
2131    )
2132}
2133
2134fn compute_prepared_reference_digest_values_with_options(
2135    xml: &str,
2136    transform_options: TransformOptions,
2137    policy: Option<&crate::policy::SigningPolicy>,
2138    provider: &dyn crate::provider::CryptoProvider,
2139    execution_budget: &TransformExecutionBudget,
2140    target_signature: usize,
2141    id_attributes: &[crate::IdAttributeRegistration],
2142) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2143    let resource_policy = policy
2144        .map(|policy| &policy.resources)
2145        .cloned()
2146        .unwrap_or_default();
2147    let external_resources = ExternalResourceContext::new(
2148        None,
2149        resource_policy.max_external_resource_bytes,
2150        resource_policy.max_external_resource_total_bytes,
2151    );
2152    let doc = parse_signing_document(
2153        xml,
2154        policy,
2155        execution_budget.xml_parse_work(),
2156        crate::XmlBackend::default(),
2157    )?;
2158    let signature =
2159        find_signing_signature_node(&doc, SigningSignatureTarget::Index(target_signature))?;
2160    let signed_info = find_required_child(signature, "SignedInfo")?;
2161    let references = parse_signing_references(signed_info)?;
2162    validate_signing_references(&references, references.len(), policy, false)?;
2163    compute_signing_reference_digests(
2164        &doc,
2165        signature,
2166        references,
2167        transform_options,
2168        provider,
2169        execution_budget,
2170        SigningUriResolution {
2171            id_attributes,
2172            same_document_id_semantics: policy.map_or(
2173                crate::policy::SameDocumentIdSemantics::Specification,
2174                |policy| policy.transforms.same_document_id_semantics,
2175            ),
2176            external_resources: &external_resources,
2177        },
2178    )
2179}
2180
2181fn prepare_reference_digest_input<'a>(
2182    xml: &'a str,
2183    policy: Option<&crate::policy::SigningPolicy>,
2184    execution_budget: &TransformExecutionBudget,
2185    target_signature: Option<usize>,
2186) -> Result<(Cow<'a, str>, usize), SigningDigestError> {
2187    let default_policy = crate::policy::SigningPolicy::default();
2188    let effective_policy = policy.unwrap_or(&default_policy);
2189    let settings =
2190        DocumentParseSettings::from_policy(&effective_policy.xml, &effective_policy.resources);
2191    let document = XmlDocument::parse_with_settings_and_budget(
2192        xml.to_owned(),
2193        settings,
2194        execution_budget.xml_parse_work(),
2195    )
2196    .map_err(map_owned_document_digest_mutation_error)?;
2197    let target_signature = if let Some(target_signature) = target_signature {
2198        target_signature
2199    } else {
2200        document.with_view(|view| {
2201            let selected =
2202                find_signing_signature_node(view.document(), SigningSignatureTarget::Last)?;
2203            signature_index(view.document(), selected)
2204        })?
2205    };
2206    // Preserve the public compute helper's precise pre-policy errors before
2207    // applying the default generation policy to the normalized candidate.
2208    document.with_view(|view| {
2209        let signature = find_signing_signature_node(
2210            view.document(),
2211            SigningSignatureTarget::Index(target_signature),
2212        )?;
2213        let signed_info = find_required_child(signature, "SignedInfo")?;
2214        let references = parse_signing_references(signed_info)?;
2215        validate_signing_references(&references, references.len(), policy, false)
2216    })?;
2217    let materialized = materialize_second_edition_c14n11_candidate(
2218        &document,
2219        target_signature,
2220        effective_policy,
2221        false,
2222    )?;
2223    Ok((
2224        materialized.map_or(Cow::Borrowed(xml), Cow::Owned),
2225        target_signature,
2226    ))
2227}
2228
2229fn fill_reference_digest_values_in_dependency_order_with_operation(
2230    document: &mut XmlDocument,
2231    transform_options: TransformOptions,
2232    provider: &dyn crate::provider::CryptoProvider,
2233    operation: &mut OperationExecutionContext<
2234        crate::policy::SigningPolicy,
2235        &mut SigningOperationBudgets,
2236    >,
2237    request: SigningReferenceRequest<'_, '_>,
2238    setup_gate: Option<OperationNodeId>,
2239) -> Result<SigningPlanNodes, SigningDigestError> {
2240    let policy = operation.policy().clone();
2241    let reference_limit = policy
2242        .resources
2243        .max_references
2244        .min(MAX_REFERENCES_PER_SIGNATURE);
2245    let process_manifests =
2246        policy.manifest_processing == crate::policy::ManifestProcessing::Process;
2247    let (signed_info_references, manifest_references) = {
2248        let budgets = operation.budgets_mut();
2249        document.with_view(|view| {
2250            let signature = find_signing_signature_node(
2251                view.document(),
2252                SigningSignatureTarget::Index(request.target_signature),
2253            )?;
2254            let signed_info = find_required_child(signature, "SignedInfo")?;
2255            let signed_info_references =
2256                parse_signing_references_with_budget(signed_info, &mut budgets.xpath_parse)?;
2257            validate_signing_references(
2258                &signed_info_references,
2259                signed_info_references.len(),
2260                Some(&policy),
2261                request.external_resources.is_configured(),
2262            )?;
2263            let manifest_references = if process_manifests {
2264                parse_signing_manifest_references(
2265                    signature,
2266                    &mut budgets.xpath_parse,
2267                    reference_limit.saturating_sub(signed_info_references.len()),
2268                    reference_limit,
2269                )?
2270            } else {
2271                Vec::new()
2272            };
2273            Ok::<_, SigningDigestError>((signed_info_references, manifest_references))
2274        })?
2275    };
2276    let total_references = signed_info_references
2277        .len()
2278        .checked_add(manifest_references.len())
2279        .ok_or_else(|| SigningDigestError::InvalidStructure("reference count overflow".into()))?;
2280    validate_signing_references(
2281        &manifest_references,
2282        total_references,
2283        Some(&policy),
2284        request.external_resources.is_configured(),
2285    )?;
2286    let placeholder = "AA==";
2287    // SignatureValue is the final mutable value in the signing pipeline. Give
2288    // it concrete character data during analysis so references that retain the
2289    // existing or future text cannot be mistaken for stable inputs.
2290    let analysis_replacements = document.with_view(|view| {
2291        let signature = find_signing_signature_node(
2292            view.document(),
2293            SigningSignatureTarget::Index(request.target_signature),
2294        )?;
2295        let signature_value = find_required_child(signature, "SignatureValue")?;
2296        let mut replacements = signed_info_references
2297            .iter()
2298            .chain(&manifest_references)
2299            .map(|reference| {
2300                (
2301                    view.node_identity_by_id(reference.digest_value_node_id),
2302                    placeholder.to_owned(),
2303                )
2304            })
2305            .collect::<Vec<_>>();
2306        replacements.push((view.node_identity(signature_value), placeholder.to_owned()));
2307        Ok::<_, SigningDigestError>(replacements)
2308    })?;
2309    let analysis_xml = document
2310        .project_base64_contents(
2311            &analysis_replacements,
2312            policy.resources.max_xml_document_bytes,
2313        )
2314        .map_err(map_owned_document_digest_mutation_error)?;
2315    let analysis_doc = parse_signing_document(
2316        &analysis_xml,
2317        Some(&policy),
2318        operation.budgets().transforms.xml_parse_work(),
2319        document.xml_backend(),
2320    )?;
2321    let analysis_signature = find_signing_signature_node(
2322        &analysis_doc,
2323        SigningSignatureTarget::Index(request.target_signature),
2324    )?;
2325    let analysis_signed_info = find_required_child(analysis_signature, "SignedInfo")?;
2326    let mut analysis_references = parse_signing_references_with_budget(
2327        analysis_signed_info,
2328        &mut operation.budgets_mut().xpath_parse,
2329    )?;
2330    if process_manifests {
2331        analysis_references.extend(parse_signing_manifest_references(
2332            analysis_signature,
2333            &mut operation.budgets_mut().xpath_parse,
2334            reference_limit.saturating_sub(signed_info_references.len()),
2335            reference_limit,
2336        )?);
2337    }
2338    let dependency_plan = reference_dependency_levels(
2339        &analysis_doc,
2340        analysis_signature,
2341        &analysis_references,
2342        transform_options,
2343        &operation.budgets().transforms,
2344        SigningUriResolution {
2345            id_attributes: request.id_attributes,
2346            same_document_id_semantics: policy.transforms.same_document_id_semantics,
2347            external_resources: request.external_resources,
2348        },
2349    )?;
2350    let (node_levels, plan_nodes) =
2351        compile_signing_operation_plan(operation, &dependency_plan, setup_gate)?;
2352    for (level, node_level) in dependency_plan.levels.into_iter().zip(node_levels) {
2353        operation.run_document_transition_batch_with_budgets(
2354            &node_level,
2355            document,
2356            |document, budgets| {
2357                let replacements = document.with_view(|view| {
2358                    let current_doc = view.document();
2359                    let current_signature = find_signing_signature_node(
2360                        current_doc,
2361                        SigningSignatureTarget::Index(request.target_signature),
2362                    )?;
2363                    let current_signed_info = find_required_child(current_signature, "SignedInfo")?;
2364                    let current_signed_info_references = parse_signing_references_with_budget(
2365                        current_signed_info,
2366                        &mut budgets.xpath_parse,
2367                    )?;
2368                    let current_manifest_references = if process_manifests {
2369                        parse_signing_manifest_references(
2370                            current_signature,
2371                            &mut budgets.xpath_parse,
2372                            reference_limit.saturating_sub(signed_info_references.len()),
2373                            reference_limit,
2374                        )?
2375                    } else {
2376                        Vec::new()
2377                    };
2378                    if current_signed_info_references.len() != signed_info_references.len()
2379                        || current_manifest_references.len() != manifest_references.len()
2380                    {
2381                        return Err(SigningDigestError::InvalidStructure(
2382                            "signing Reference set changed while filling digests".into(),
2383                        ));
2384                    }
2385                    let mut destinations = Vec::with_capacity(level.len());
2386                    let mut level_references = Vec::with_capacity(level.len());
2387                    for index in &level {
2388                        let reference = if *index < signed_info_references.len() {
2389                            current_signed_info_references.get(*index)
2390                        } else {
2391                            current_manifest_references.get(*index - signed_info_references.len())
2392                        }
2393                        .ok_or_else(|| {
2394                            SigningDigestError::InvalidStructure(
2395                                "signing Reference set changed while filling digests".into(),
2396                            )
2397                        })?;
2398                        destinations.push(view.node_identity_by_id(reference.digest_value_node_id));
2399                        level_references.push(reference.clone());
2400                    }
2401                    let computed = compute_signing_reference_digests(
2402                        current_doc,
2403                        current_signature,
2404                        level_references,
2405                        transform_options,
2406                        provider,
2407                        &budgets.transforms,
2408                        SigningUriResolution {
2409                            id_attributes: request.id_attributes,
2410                            same_document_id_semantics: policy
2411                                .transforms
2412                                .same_document_id_semantics,
2413                            external_resources: request.external_resources,
2414                        },
2415                    )?;
2416                    if computed.len() != destinations.len() {
2417                        return Err(SigningDigestError::InvalidStructure(
2418                            "signing Reference set changed while computing digests".into(),
2419                        ));
2420                    }
2421                    Ok::<_, SigningDigestError>(
2422                        destinations
2423                            .into_iter()
2424                            .zip(computed)
2425                            .map(|(target, digest)| (target, digest.digest_value))
2426                            .collect::<Vec<_>>(),
2427                    )
2428                })?;
2429                document
2430                    .replace_base64_contents_with_budget(
2431                        &replacements,
2432                        DocumentParseSettings::from_policy(&policy.xml, &policy.resources)
2433                            .with_backend(document.xml_backend()),
2434                        budgets.transforms.xml_parse_work(),
2435                    )
2436                    .map_err(map_owned_document_digest_mutation_error)
2437            },
2438        )?;
2439    }
2440    Ok(plan_nodes)
2441}
2442
2443#[cfg(test)]
2444fn fill_reference_digest_values_in_dependency_order(
2445    document: &mut XmlDocument,
2446    transform_options: TransformOptions,
2447    policy: &crate::policy::SigningPolicy,
2448    provider: &dyn crate::provider::CryptoProvider,
2449    budgets: &mut SigningOperationBudgets,
2450    target_signature: usize,
2451    id_attributes: &[crate::IdAttributeRegistration],
2452) -> Result<(), SigningDigestError> {
2453    let external_resources = ExternalResourceContext::new(
2454        None,
2455        policy.resources.max_external_resource_bytes,
2456        policy.resources.max_external_resource_total_bytes,
2457    );
2458    let binding = (document.identity(), document.generation());
2459    let mut operation = OperationExecutionContext::new(policy.clone(), budgets, Some(binding));
2460    fill_reference_digest_values_in_dependency_order_with_operation(
2461        document,
2462        transform_options,
2463        provider,
2464        &mut operation,
2465        SigningReferenceRequest {
2466            target_signature,
2467            id_attributes,
2468            external_resources: &external_resources,
2469        },
2470        None,
2471    )
2472    .map(|_| ())
2473}
2474
2475struct SigningDependencyPlan {
2476    dependencies: Vec<HashSet<usize>>,
2477    levels: Vec<Vec<usize>>,
2478}
2479
2480fn reference_dependency_levels(
2481    doc: &Document<'_>,
2482    signature: Node<'_, '_>,
2483    references: &[SigningReference],
2484    transform_options: TransformOptions,
2485    execution_budget: &TransformExecutionBudget,
2486    uri_resolution: SigningUriResolution<'_, '_>,
2487) -> Result<SigningDependencyPlan, SigningDigestError> {
2488    let resolver = uri_resolution.external_resources.bind(
2489        doc,
2490        uri_resolution.id_attributes,
2491        uri_resolution.same_document_id_semantics,
2492    );
2493    let terminal_signature_value_index = references.len();
2494    let signature_value = find_required_child(signature, "SignatureValue")?;
2495    let mut tracked_mutable_nodes = references
2496        .iter()
2497        .enumerate()
2498        .flat_map(|(index, reference)| {
2499            std::iter::once((index, reference.digest_value_node_id)).chain(
2500                doc.get_node(reference.digest_value_node_id)
2501                    .into_iter()
2502                    .flat_map(|node| node.children())
2503                    .filter(|node| node.is_text())
2504                    .map(move |node| (index, node.id())),
2505            )
2506        })
2507        .collect::<Vec<_>>();
2508    tracked_mutable_nodes.push((terminal_signature_value_index, signature_value.id()));
2509    tracked_mutable_nodes.extend(
2510        signature_value
2511            .children()
2512            .filter(|node| node.is_text())
2513            .map(|node| (terminal_signature_value_index, node.id())),
2514    );
2515    let analyses = references
2516        .iter()
2517        .map(|reference| {
2518            let origin = signing_reference_origin(doc, reference)?;
2519            let initial_data = resolver.dereference_from_with_budget(
2520                &reference.uri,
2521                origin,
2522                execution_budget.node_set_materialization(),
2523                execution_budget.xml_base_resolution(),
2524            )?;
2525            let output = execute_transforms_with_dependency_nodes(
2526                signature,
2527                initial_data,
2528                &reference.transforms,
2529                transform_options,
2530                execution_budget,
2531                tracked_mutable_nodes.clone(),
2532            )?;
2533            Ok(output.dependencies)
2534        })
2535        .collect::<Result<Vec<_>, SigningDigestError>>()?;
2536    if analyses
2537        .iter()
2538        .any(|dependencies| dependencies.contains(&terminal_signature_value_index))
2539    {
2540        return Err(SigningDigestError::InvalidStructure(
2541            "Reference dependency cycle includes the mutable SignatureValue".into(),
2542        ));
2543    }
2544    let mut remaining_dependencies = analyses.clone();
2545    let mut completed = vec![false; references.len()];
2546    let mut levels = Vec::new();
2547    while completed.iter().any(|done| !done) {
2548        let ready = remaining_dependencies
2549            .iter()
2550            .enumerate()
2551            .filter_map(|(index, dependencies)| {
2552                (!completed[index] && dependencies.is_empty()).then_some(index)
2553            })
2554            .collect::<Vec<_>>();
2555        if ready.is_empty() {
2556            return Err(SigningDigestError::InvalidStructure(
2557                "Manifest Reference digest dependency cycle".into(),
2558            ));
2559        }
2560        for index in &ready {
2561            completed[*index] = true;
2562        }
2563        for dependency_set in &mut remaining_dependencies {
2564            dependency_set.retain(|dependency| !completed[*dependency]);
2565        }
2566        levels.push(ready);
2567    }
2568
2569    // Every mutable DigestValue belongs to the selected Signature;
2570    // an enveloped transform excludes that complete subtree and therefore
2571    // removes all such dependencies from its input node-set.
2572    debug_assert!(references.iter().all(|reference| {
2573        signature.range().start <= reference.digest_value_range.start
2574            && signature.range().end >= reference.digest_value_range.end
2575    }));
2576    Ok(SigningDependencyPlan {
2577        dependencies: analyses,
2578        levels,
2579    })
2580}
2581
2582fn signing_reference_origin<'doc, 'input>(
2583    doc: &'doc Document<'input>,
2584    reference: &SigningReference,
2585) -> Result<Node<'doc, 'input>, SigningDigestError> {
2586    doc.get_node(reference.origin_node_id).ok_or_else(|| {
2587        SigningDigestError::InvalidStructure(
2588            "signing Reference origin changed while computing digests".into(),
2589        )
2590    })
2591}
2592
2593fn compile_signing_operation_plan(
2594    operation: &mut OperationExecutionContext<
2595        crate::policy::SigningPolicy,
2596        &mut SigningOperationBudgets,
2597    >,
2598    dependency_plan: &SigningDependencyPlan,
2599    setup_gate: Option<OperationNodeId>,
2600) -> Result<(Vec<Vec<OperationNodeId>>, SigningPlanNodes), SigningDigestError> {
2601    let reference_count = dependency_plan.levels.iter().map(Vec::len).sum::<usize>();
2602    let mut digest_nodes = vec![None; reference_count];
2603    for (index, digest_slot) in digest_nodes.iter_mut().enumerate() {
2604        let digest_node = operation.add_node(
2605            OperationNodeKind::Digest { index },
2606            OperationStage::Digest,
2607            None,
2608        );
2609        if let Some(setup_gate) = setup_gate {
2610            operation
2611                .add_dependency(digest_node, setup_gate)
2612                .map_err(map_signing_digest_plan_error)?;
2613        }
2614        *digest_slot = Some(digest_node);
2615    }
2616
2617    let mut node_levels = Vec::with_capacity(dependency_plan.levels.len());
2618    for level in &dependency_plan.levels {
2619        let nodes = level
2620            .iter()
2621            .map(|index| {
2622                digest_nodes
2623                    .get(*index)
2624                    .and_then(|node| *node)
2625                    .ok_or_else(|| {
2626                        SigningDigestError::InvalidStructure(
2627                            "compiled signing Reference index is unavailable".into(),
2628                        )
2629                    })
2630            })
2631            .collect::<Result<Vec<_>, _>>()?;
2632        for (index, node) in level.iter().zip(&nodes) {
2633            for dependency_index in &dependency_plan.dependencies[*index] {
2634                let dependency = digest_nodes
2635                    .get(*dependency_index)
2636                    .and_then(|node| *node)
2637                    .ok_or_else(|| {
2638                        SigningDigestError::InvalidStructure(
2639                            "compiled signing dependency index is unavailable".into(),
2640                        )
2641                    })?;
2642                operation
2643                    .add_dependency(*node, dependency)
2644                    .map_err(map_signing_digest_plan_error)?;
2645            }
2646        }
2647        node_levels.push(nodes);
2648    }
2649
2650    let canonicalization = operation.add_node(
2651        OperationNodeKind::Canonicalization,
2652        OperationStage::Canonicalization,
2653        None,
2654    );
2655    for digest in digest_nodes.iter().flatten() {
2656        operation
2657            .add_dependency(canonicalization, *digest)
2658            .map_err(map_signing_digest_plan_error)?;
2659    }
2660    let crypto = operation.add_node(OperationNodeKind::Crypto, OperationStage::Crypto, None);
2661    operation
2662        .add_dependency(crypto, canonicalization)
2663        .map_err(map_signing_digest_plan_error)?;
2664    let evidence = operation.add_node(OperationNodeKind::Evidence, OperationStage::Evidence, None);
2665    operation
2666        .add_dependency(evidence, crypto)
2667        .map_err(map_signing_digest_plan_error)?;
2668    let mutation = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
2669    operation
2670        .add_dependency(mutation, evidence)
2671        .map_err(map_signing_digest_plan_error)?;
2672    operation.compile().map_err(map_signing_digest_plan_error)?;
2673
2674    Ok((
2675        node_levels,
2676        SigningPlanNodes {
2677            canonicalization,
2678            crypto,
2679            evidence,
2680            mutation,
2681        },
2682    ))
2683}
2684
2685fn map_signing_digest_plan_error(error: OperationPlanError) -> SigningDigestError {
2686    SigningDigestError::InvalidStructure(error.to_string())
2687}
2688
2689fn commit_signed_staged(
2690    document: &mut XmlDocument,
2691    staged: XmlDocument,
2692    policy: &crate::policy::SigningPolicy,
2693) -> Result<(), SigningError> {
2694    let mut operation = OperationExecutionContext::new(
2695        policy.clone(),
2696        (),
2697        Some((document.identity(), document.generation())),
2698    );
2699    let mutation = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
2700    operation.compile()?;
2701    operation.run_document_transition(mutation, document, |document, _| {
2702        document
2703            .commit_staged(staged)
2704            .map_err(map_owned_document_mutation_error)
2705    })
2706}
2707
2708fn validate_signing_references(
2709    references: &[SigningReference],
2710    total_references: usize,
2711    policy: Option<&crate::policy::SigningPolicy>,
2712    has_external_resources: bool,
2713) -> Result<(), SigningDigestError> {
2714    if let Some(policy) = policy
2715        && total_references > policy.resources.max_references
2716    {
2717        return Err(crate::policy::PolicyViolation::ResourceLimit {
2718            resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
2719            maximum: policy.resources.max_references,
2720            actual: total_references,
2721        }
2722        .into());
2723    }
2724    for reference in references {
2725        if let Some(policy) = policy {
2726            validate_signing_reference_uri(&reference.uri, policy)?;
2727            validate_signing_reference_request(&reference.uri, has_external_resources)?;
2728        }
2729        if let Some(policy) = policy
2730            && reference.transforms.len() > policy.resources.max_transforms_per_reference
2731        {
2732            return Err(crate::policy::PolicyViolation::ResourceLimit {
2733                resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
2734                maximum: policy.resources.max_transforms_per_reference,
2735                actual: reference.transforms.len(),
2736            }
2737            .into());
2738        }
2739        if let Some(policy) = policy {
2740            policy.check_digest_algorithm(reference.digest_method)?;
2741        } else if !reference.digest_method.signing_allowed() {
2742            return Err(SigningDigestError::SigningAlgorithmDisabled {
2743                uri: reference.digest_method.uri(),
2744            });
2745        }
2746        let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
2747        validate_signing_transform_policy(
2748            initial_binary,
2749            &reference.transforms,
2750            policy.and_then(|policy| policy.transforms.allowed_algorithms.as_ref()),
2751        )?;
2752    }
2753    Ok(())
2754}
2755
2756fn validate_signing_signed_info_methods(
2757    signature: Node<'_, '_>,
2758    policy: &crate::policy::SigningPolicy,
2759) -> Result<(), SigningDigestError> {
2760    let signed_info = find_required_child(signature, "SignedInfo")?;
2761    let canonicalization_method =
2762        element_children(signed_info)
2763            .next()
2764            .ok_or(SigningDigestError::MissingElement {
2765                element: "CanonicalizationMethod",
2766            })?;
2767    verify_ds_element(canonicalization_method, "CanonicalizationMethod")?;
2768    let algorithm = required_algorithm_attr(canonicalization_method, "CanonicalizationMethod")?;
2769    if policy
2770        .transforms
2771        .allowed_algorithms
2772        .as_ref()
2773        .is_some_and(|allowed| !allowed.contains(algorithm))
2774    {
2775        return Err(crate::policy::PolicyViolation::Algorithm {
2776            operation: "SignedInfo canonicalization",
2777            algorithm: algorithm.to_owned(),
2778        }
2779        .into());
2780    }
2781    Ok(())
2782}
2783
2784struct SigningUriResolution<'a, 'resources> {
2785    id_attributes: &'a [crate::IdAttributeRegistration],
2786    same_document_id_semantics: crate::policy::SameDocumentIdSemantics,
2787    external_resources: &'a ExternalResourceContext<'resources>,
2788}
2789
2790fn compute_signing_reference_digests(
2791    doc: &Document<'_>,
2792    signature: Node<'_, '_>,
2793    references: Vec<SigningReference>,
2794    transform_options: TransformOptions,
2795    provider: &dyn crate::provider::CryptoProvider,
2796    execution_budget: &TransformExecutionBudget,
2797    uri_resolution: SigningUriResolution<'_, '_>,
2798) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2799    let resolver = uri_resolution.external_resources.bind(
2800        doc,
2801        uri_resolution.id_attributes,
2802        uri_resolution.same_document_id_semantics,
2803    );
2804    references
2805        .into_iter()
2806        .enumerate()
2807        .map(|(index, reference)| {
2808            let origin = signing_reference_origin(doc, &reference)?;
2809            let initial_data = resolver.dereference_from_with_budget(
2810                &reference.uri,
2811                origin,
2812                execution_budget.node_set_materialization(),
2813                execution_budget.xml_base_resolution(),
2814            )?;
2815            let pre_digest = execute_transforms_with_options_and_budget(
2816                signature,
2817                initial_data,
2818                &reference.transforms,
2819                transform_options,
2820                execution_budget,
2821            )?;
2822            let digest = super::compute_digest_with_provider(
2823                provider,
2824                reference.digest_method,
2825                &pre_digest,
2826            )?;
2827            let digest_value = base64::engine::general_purpose::STANDARD.encode(digest);
2828            Ok(ComputedReferenceDigest {
2829                index,
2830                uri: reference.uri,
2831                digest_method: reference.digest_method,
2832                digest_value,
2833            })
2834        })
2835        .collect()
2836}
2837
2838/// Compute and fill all signing-template `<DigestValue>` elements.
2839///
2840/// This is the signing counterpart to verification reference processing: it
2841/// dereferences each `<Reference>`, applies transforms, computes the digest,
2842/// and writes the base64 digest into the matching `<DigestValue>` in document
2843/// order. As required for Second Edition generation, any reference chain that
2844/// still produces a node-set is first made explicit with Canonical XML 1.1.
2845pub fn fill_reference_digest_values(xml: &str) -> Result<String, SigningDigestError> {
2846    let execution_budget = TransformExecutionBudget::default();
2847    fill_reference_digest_values_with_options(
2848        xml,
2849        TransformOptions::default(),
2850        None,
2851        crate::provider::default_provider(),
2852        &execution_budget,
2853        None,
2854        &[],
2855    )
2856}
2857
2858fn fill_reference_digest_values_with_options(
2859    xml: &str,
2860    transform_options: TransformOptions,
2861    policy: Option<&crate::policy::SigningPolicy>,
2862    provider: &dyn crate::provider::CryptoProvider,
2863    execution_budget: &TransformExecutionBudget,
2864    target_signature: Option<usize>,
2865    id_attributes: &[crate::IdAttributeRegistration],
2866) -> Result<String, SigningDigestError> {
2867    let default_policy = crate::policy::SigningPolicy::default();
2868    let effective_policy = policy.unwrap_or(&default_policy);
2869    let (prepared, target_signature) =
2870        prepare_reference_digest_input(xml, policy, execution_budget, target_signature)?;
2871    let digest_values = compute_prepared_reference_digest_values_with_options(
2872        prepared.as_ref(),
2873        transform_options,
2874        Some(effective_policy),
2875        provider,
2876        execution_budget,
2877        target_signature,
2878        id_attributes,
2879    )?
2880    .into_iter()
2881    .map(|digest| digest.digest_value);
2882    Ok(fill_signed_info_digest_values_at_index_with_budget(
2883        prepared.as_ref(),
2884        digest_values,
2885        target_signature,
2886        Some(effective_policy),
2887        Some(execution_budget.xml_parse_work()),
2888    )?)
2889}
2890
2891fn canonicalize_signed_info(
2892    document: &XmlDocument,
2893    policy: &crate::policy::SigningPolicy,
2894    budgets: &mut SigningOperationBudgets,
2895    target_signature: usize,
2896) -> Result<(SignatureAlgorithm, Option<usize>, Vec<u8>), SigningError> {
2897    document.with_view(|view| {
2898        let doc = view.document();
2899        let signature =
2900            find_signing_signature_node(doc, SigningSignatureTarget::Index(target_signature))
2901                .map_err(SigningError::Digest)?;
2902        let signed_info_node =
2903            find_required_child(signature, "SignedInfo").map_err(SigningError::Digest)?;
2904        let signed_info =
2905            parse_signed_info_with_xpath_budget(signed_info_node, &mut budgets.xpath_parse)?;
2906        if policy
2907            .transforms
2908            .allowed_algorithms
2909            .as_ref()
2910            .is_some_and(|allowed| !allowed.contains(signed_info.c14n_method.uri()))
2911        {
2912            return Err(crate::policy::PolicyViolation::Algorithm {
2913                operation: "SignedInfo canonicalization",
2914                algorithm: signed_info.c14n_method.uri().to_owned(),
2915            }
2916            .into());
2917        }
2918        let signed_info_subtree: HashSet<_> = signed_info_node
2919            .descendants()
2920            .map(|node: Node<'_, '_>| node.id())
2921            .collect();
2922        let mut canonical_signed_info = Vec::new();
2923        canonicalize_bounded_with_xml_base_budget(
2924            doc,
2925            Some(&|node| signed_info_subtree.contains(&node.id())),
2926            &signed_info.c14n_method,
2927            budgets.transforms.remaining_c14n_output(),
2928            budgets.transforms.xml_base_resolution(),
2929            &mut canonical_signed_info,
2930        )
2931        .map_err(|error| {
2932            if let Some(violation) = map_c14n_resource_policy_violation(
2933                &error,
2934                crate::policy::resource_name::CANONICALIZED_BYTES,
2935                budgets.transforms.c14n_output_limit(),
2936            ) {
2937                SigningError::Policy(violation)
2938            } else {
2939                SigningError::Canonicalization(error)
2940            }
2941        })?;
2942        Ok((
2943            signed_info.signature_method,
2944            signed_info.hmac_output_length_bits,
2945            canonical_signed_info,
2946        ))
2947    })
2948}
2949
2950const SECOND_EDITION_GENERATION_C14N_URI: &str = "http://www.w3.org/2006/12/xml-c14n11";
2951
2952struct SourceEdit {
2953    range: Range<usize>,
2954    replacement: String,
2955}
2956
2957fn materialize_second_edition_c14n11_candidate(
2958    document: &XmlDocument,
2959    target_signature: usize,
2960    policy: &crate::policy::SigningPolicy,
2961    has_external_resources: bool,
2962) -> Result<Option<String>, SigningDigestError> {
2963    let edits = document.with_view(|view| {
2964        let signature = find_signing_signature_node(
2965            view.document(),
2966            SigningSignatureTarget::Index(target_signature),
2967        )?;
2968        let signed_info = find_required_child(signature, "SignedInfo")?;
2969        let mut reference_nodes = element_children(signed_info)
2970            .filter(|node| node.has_tag_name((XMLDSIG_NS, "Reference")))
2971            .collect::<Vec<_>>();
2972        if policy.manifest_processing == crate::policy::ManifestProcessing::Process {
2973            let reference_limit = policy
2974                .resources
2975                .max_references
2976                .min(MAX_REFERENCES_PER_SIGNATURE);
2977            for manifest in signature
2978                .children()
2979                .filter(|node| node.has_tag_name((XMLDSIG_NS, "Object")))
2980                .flat_map(|object| {
2981                    object
2982                        .children()
2983                        .filter(|node| node.has_tag_name((XMLDSIG_NS, "Manifest")))
2984                })
2985            {
2986                let mut manifest_references = 0usize;
2987                for child in element_children(manifest) {
2988                    verify_ds_element(child, "Reference")?;
2989                    if reference_nodes.len() == reference_limit {
2990                        return Err(crate::policy::PolicyViolation::ResourceLimit {
2991                            resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
2992                            maximum: reference_limit,
2993                            actual: reference_limit.saturating_add(1),
2994                        }
2995                        .into());
2996                    }
2997                    reference_nodes.push(child);
2998                    manifest_references += 1;
2999                }
3000                if manifest_references == 0 {
3001                    return Err(SigningDigestError::MissingElement {
3002                        element: "Reference",
3003                    });
3004                }
3005            }
3006        }
3007        if reference_nodes.len() > policy.resources.max_references {
3008            return Err(crate::policy::PolicyViolation::ResourceLimit {
3009                resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
3010                maximum: policy.resources.max_references,
3011                actual: reference_nodes.len(),
3012            }
3013            .into());
3014        }
3015
3016        let mut edits = Vec::new();
3017        for reference_node in reference_nodes {
3018            let structure = parse_signing_reference_structure(reference_node)?;
3019            validate_signing_reference_uri(structure.uri, policy)?;
3020            validate_signing_reference_request(structure.uri, has_external_resources)?;
3021            let digest_uri = required_algorithm_attr(structure.digest_method_node, "DigestMethod")?;
3022            let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
3023                SigningDigestError::UnsupportedAlgorithm {
3024                    uri: digest_uri.to_owned(),
3025                }
3026            })?;
3027            policy.check_digest_algorithm(digest_method)?;
3028
3029            let transform_uris = generation_transform_uris(structure.transforms_node)?;
3030            if transform_uris.len() > policy.resources.max_transforms_per_reference {
3031                return Err(crate::policy::PolicyViolation::ResourceLimit {
3032                    resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3033                    maximum: policy.resources.max_transforms_per_reference,
3034                    actual: transform_uris.len(),
3035                }
3036                .into());
3037            }
3038            if let Some(allowed) = policy.transforms.allowed_algorithms.as_ref()
3039                && let Some(disallowed) = transform_uris
3040                    .iter()
3041                    .find(|algorithm| !allowed.contains(**algorithm))
3042            {
3043                return Err(crate::policy::PolicyViolation::Algorithm {
3044                    operation: "signing transform",
3045                    algorithm: (*disallowed).to_owned(),
3046                }
3047                .into());
3048            }
3049            let initial_binary = !structure.uri.is_empty() && !structure.uri.starts_with('#');
3050            if generation_chain_produces_binary(initial_binary, &transform_uris) {
3051                continue;
3052            }
3053            if transform_uris.len() >= policy.resources.max_transforms_per_reference {
3054                return Err(crate::policy::PolicyViolation::ResourceLimit {
3055                    resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3056                    maximum: policy.resources.max_transforms_per_reference,
3057                    actual: transform_uris.len().saturating_add(1),
3058                }
3059                .into());
3060            }
3061            if policy
3062                .transforms
3063                .allowed_algorithms
3064                .as_ref()
3065                .is_some_and(|allowed| !allowed.contains(SECOND_EDITION_GENERATION_C14N_URI))
3066            {
3067                return Err(crate::policy::PolicyViolation::Algorithm {
3068                    operation: "signing transform",
3069                    algorithm: SECOND_EDITION_GENERATION_C14N_URI.to_owned(),
3070                }
3071                .into());
3072            }
3073
3074            let edit = if let Some(transforms_node) = structure.transforms_node {
3075                c14n11_edit_for_transforms(view.xml(), transforms_node)?
3076            } else {
3077                let prefix = reference_node.prefix().unwrap_or_default();
3078                let qualifier = xml_qualifier(prefix);
3079                SourceEdit {
3080                    range: structure.digest_method_node.range().start
3081                        ..structure.digest_method_node.range().start,
3082                    replacement: format!(
3083                        "<{qualifier}Transforms><{qualifier}Transform Algorithm=\"{SECOND_EDITION_GENERATION_C14N_URI}\"/></{qualifier}Transforms>"
3084                    ),
3085                }
3086            };
3087            edits.push(edit);
3088        }
3089        Ok::<_, SigningDigestError>(edits)
3090    })?;
3091    if edits.is_empty() {
3092        return Ok(None);
3093    }
3094
3095    let projected_len = edits
3096        .iter()
3097        .try_fold(document.as_xml().len(), |total, edit| {
3098            total
3099                .checked_sub(edit.range.len())
3100                .and_then(|value| value.checked_add(edit.replacement.len()))
3101                .ok_or_else(|| {
3102                    SigningDigestError::InvalidStructure("document length overflow".into())
3103                })
3104        })?;
3105    policy
3106        .resources
3107        .validate_xml_document_len(projected_len)
3108        .map_err(SigningDigestError::Policy)?;
3109
3110    let mut candidate = document.as_xml().to_owned();
3111    let mut edits = edits;
3112    edits.sort_unstable_by_key(|edit| std::cmp::Reverse(edit.range.start));
3113    for edit in edits {
3114        candidate.replace_range(edit.range, &edit.replacement);
3115    }
3116    Ok(Some(candidate))
3117}
3118
3119fn c14n11_edit_for_transforms(
3120    xml: &str,
3121    transforms_node: Node<'_, '_>,
3122) -> Result<SourceEdit, SigningDigestError> {
3123    let range = transforms_node.range();
3124    let source = xml.get(range.clone()).ok_or_else(|| {
3125        SigningDigestError::InvalidStructure("Transforms source range is unavailable".into())
3126    })?;
3127    let prefix = transforms_node.prefix().unwrap_or_default();
3128    let qualifier = xml_qualifier(prefix);
3129    let transform =
3130        format!("<{qualifier}Transform Algorithm=\"{SECOND_EDITION_GENERATION_C14N_URI}\"/>");
3131    if source.trim_end().ends_with("/>") {
3132        let trimmed = source.trim_end();
3133        let opening = trimmed.strip_suffix("/>").ok_or_else(|| {
3134            SigningDigestError::InvalidStructure(
3135                "self-closing Transforms source is unavailable".into(),
3136            )
3137        })?;
3138        let trailing = &source[trimmed.len()..];
3139        return Ok(SourceEdit {
3140            range,
3141            replacement: format!("{opening}>{transform}</{qualifier}Transforms>{trailing}"),
3142        });
3143    }
3144    let closing = source.rfind("</").ok_or_else(|| {
3145        SigningDigestError::InvalidStructure("Transforms closing tag is unavailable".into())
3146    })?;
3147    let offset = range.start + closing;
3148    Ok(SourceEdit {
3149        range: offset..offset,
3150        replacement: transform,
3151    })
3152}
3153
3154fn generation_transform_uris<'a>(
3155    transforms_node: Option<Node<'a, 'a>>,
3156) -> Result<Vec<&'a str>, SigningDigestError> {
3157    let Some(transforms_node) = transforms_node else {
3158        return Ok(Vec::new());
3159    };
3160    let mut algorithms = Vec::new();
3161    for child in element_children(transforms_node) {
3162        if algorithms.len() == MAX_TRANSFORMS_PER_REFERENCE {
3163            return Err(crate::policy::PolicyViolation::ResourceLimit {
3164                resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3165                maximum: MAX_TRANSFORMS_PER_REFERENCE,
3166                actual: MAX_TRANSFORMS_PER_REFERENCE.saturating_add(1),
3167            }
3168            .into());
3169        }
3170        if !child.has_tag_name((XMLDSIG_NS, "Transform")) {
3171            return Err(TransformError::UnsupportedTransform(
3172                "unexpected child element of <ds:Transforms>; only <ds:Transform> is allowed"
3173                    .into(),
3174            )
3175            .into());
3176        }
3177        let algorithm = child.attribute("Algorithm").ok_or_else(|| {
3178            TransformError::UnsupportedTransform(
3179                "missing Algorithm attribute on <Transform>".into(),
3180            )
3181        })?;
3182        if algorithm != ENVELOPED_SIGNATURE_URI
3183            && algorithm != BASE64_TRANSFORM_URI
3184            && algorithm != XPATH_TRANSFORM_URI
3185            && algorithm != XPATH_FILTER2_TRANSFORM_URI
3186            && crate::c14n::C14nAlgorithm::from_uri(algorithm).is_none()
3187        {
3188            return Err(TransformError::UnsupportedTransform(algorithm.to_owned()).into());
3189        }
3190        algorithms.push(algorithm);
3191    }
3192    Ok(algorithms)
3193}
3194
3195fn generation_chain_produces_binary(initial_binary: bool, algorithms: &[&str]) -> bool {
3196    algorithms.last().map_or(initial_binary, |algorithm| {
3197        *algorithm == BASE64_TRANSFORM_URI
3198            || crate::c14n::C14nAlgorithm::from_uri(algorithm).is_some()
3199    })
3200}
3201
3202fn xml_qualifier(prefix: &str) -> String {
3203    if prefix.is_empty() {
3204        String::new()
3205    } else {
3206        format!("{prefix}:")
3207    }
3208}
3209
3210fn parse_signing_document<'a>(
3211    xml: &'a str,
3212    policy: Option<&crate::policy::SigningPolicy>,
3213    budget: &XmlParseWorkBudget,
3214    backend: crate::XmlBackend,
3215) -> Result<Document<'a>, SigningDigestError> {
3216    let settings = policy
3217        .map(|policy| DocumentParseSettings::from_policy(&policy.xml, &policy.resources))
3218        .unwrap_or_default()
3219        .with_backend(backend);
3220    super::mutation::parse_with_options_and_budget(xml, settings, Some(budget)).map_err(|error| {
3221        match error.into_policy_violation(settings) {
3222            Ok(error) => SigningDigestError::Policy(error),
3223            Err(XmlDocumentError::Parse(error)) => SigningDigestError::XmlParse(error),
3224            Err(error) => SigningDigestError::Document(error),
3225        }
3226    })
3227}
3228
3229fn parse_private_key_pem(private_key_pem: &str) -> Result<Zeroizing<Vec<u8>>, SigningKeyError> {
3230    let (rest, pem) = x509_parser::pem::parse_x509_pem(private_key_pem.as_bytes())
3231        .map_err(|_| SigningKeyError::InvalidKeyPem)?;
3232    if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
3233        return Err(SigningKeyError::InvalidKeyPem);
3234    }
3235    if pem.label != "PRIVATE KEY" {
3236        return Err(SigningKeyError::InvalidKeyFormat { label: pem.label });
3237    }
3238    Ok(Zeroizing::new(pem.contents))
3239}
3240
3241enum SigningSignatureTarget {
3242    First,
3243    Last,
3244    Index(usize),
3245}
3246
3247fn find_signing_signature_node<'a>(
3248    doc: &'a Document<'a>,
3249    target: SigningSignatureTarget,
3250) -> Result<Node<'a, 'a>, SigningDigestError> {
3251    let mut signatures = doc.descendants().filter(|node| {
3252        node.is_element()
3253            && node.tag_name().name() == "Signature"
3254            && node.tag_name().namespace() == Some(XMLDSIG_NS)
3255    });
3256    match target {
3257        SigningSignatureTarget::First => signatures.next(),
3258        SigningSignatureTarget::Last => signatures.next_back(),
3259        SigningSignatureTarget::Index(index) => signatures.nth(index),
3260    }
3261    .ok_or(SigningDigestError::MissingElement {
3262        element: "Signature",
3263    })
3264}
3265
3266fn signing_signature_index(
3267    doc: &Document<'_>,
3268    start_node_id: Option<&str>,
3269    id_attributes: &[crate::IdAttributeRegistration],
3270    selection: SignatureTemplateSelection,
3271) -> Result<usize, SigningDigestError> {
3272    let selected = if let Some(id) = start_node_id {
3273        let start = signing_start_node(doc, id, id_attributes)?;
3274        let mut signatures = start
3275            .descendants()
3276            .filter(|node| node.has_tag_name((XMLDSIG_NS, "Signature")));
3277        match selection.target() {
3278            SigningSignatureTarget::First => signatures.next(),
3279            SigningSignatureTarget::Last => signatures.next_back(),
3280            SigningSignatureTarget::Index(_) => unreachable!("public selection is not indexed"),
3281        }
3282        .ok_or_else(|| {
3283            SigningDigestError::InvalidStructure(format!(
3284                "selected node subtree has no Signature: {id}"
3285            ))
3286        })?
3287    } else {
3288        find_signing_signature_node(doc, selection.target())?
3289    };
3290    signature_index(doc, selected)
3291}
3292
3293fn signing_start_node<'a>(
3294    doc: &'a Document<'a>,
3295    id: &str,
3296    id_attributes: &[crate::IdAttributeRegistration],
3297) -> Result<Node<'a, 'a>, SigningDigestError> {
3298    UriReferenceResolver::with_id_registrations(doc, id_attributes)
3299        .node_for_id(id)
3300        .ok_or_else(|| {
3301            SigningDigestError::InvalidStructure(format!(
3302                "selected node ID is missing or ambiguous: {id}"
3303            ))
3304        })
3305}
3306
3307fn signature_index(
3308    doc: &Document<'_>,
3309    selected: Node<'_, '_>,
3310) -> Result<usize, SigningDigestError> {
3311    doc.descendants()
3312        .filter(|node| node.has_tag_name((XMLDSIG_NS, "Signature")))
3313        .position(|node| node == selected)
3314        .ok_or(SigningDigestError::MissingElement {
3315            element: "Signature",
3316        })
3317}
3318
3319fn parse_signing_references(
3320    signed_info: Node<'_, '_>,
3321) -> Result<Vec<SigningReference>, SigningDigestError> {
3322    parse_signing_references_with_budget(signed_info, &mut XPathSignatureParseBudget::default())
3323}
3324
3325fn parse_signing_references_with_budget(
3326    signed_info: Node<'_, '_>,
3327    xpath_budget: &mut XPathSignatureParseBudget,
3328) -> Result<Vec<SigningReference>, SigningDigestError> {
3329    verify_ds_element(signed_info, "SignedInfo")?;
3330    let mut children = element_children(signed_info);
3331
3332    let c14n_node = children.next().ok_or(SigningDigestError::MissingElement {
3333        element: "CanonicalizationMethod",
3334    })?;
3335    verify_ds_element(c14n_node, "CanonicalizationMethod")?;
3336    required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
3337
3338    let signature_method_node = children.next().ok_or(SigningDigestError::MissingElement {
3339        element: "SignatureMethod",
3340    })?;
3341    verify_ds_element(signature_method_node, "SignatureMethod")?;
3342    required_algorithm_attr(signature_method_node, "SignatureMethod")?;
3343
3344    let mut references = Vec::new();
3345    for child in children {
3346        verify_ds_element(child, "Reference")?;
3347        if references.len() == MAX_REFERENCES_PER_SIGNATURE {
3348            return Err(SigningDigestError::InvalidStructure(format!(
3349                "SignedInfo contains more than {MAX_REFERENCES_PER_SIGNATURE} Reference elements"
3350            )));
3351        }
3352        references.push(parse_signing_reference(child, xpath_budget)?);
3353    }
3354    if references.is_empty() {
3355        return Err(SigningDigestError::MissingElement {
3356            element: "Reference",
3357        });
3358    }
3359    Ok(references)
3360}
3361
3362fn parse_signing_manifest_references(
3363    signature: Node<'_, '_>,
3364    xpath_budget: &mut XPathSignatureParseBudget,
3365    mut remaining_capacity: usize,
3366    maximum_references: usize,
3367) -> Result<Vec<SigningReference>, SigningDigestError> {
3368    let mut references = Vec::new();
3369    for manifest in signature
3370        .children()
3371        .filter(|node| node.has_tag_name((XMLDSIG_NS, "Object")))
3372        .flat_map(|object| {
3373            object
3374                .children()
3375                .filter(|node| node.has_tag_name((XMLDSIG_NS, "Manifest")))
3376        })
3377    {
3378        let mut manifest_references = 0usize;
3379        for child in element_children(manifest) {
3380            verify_ds_element(child, "Reference")?;
3381            if remaining_capacity == 0 {
3382                return Err(crate::policy::PolicyViolation::ResourceLimit {
3383                    resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
3384                    maximum: maximum_references,
3385                    actual: maximum_references.saturating_add(1),
3386                }
3387                .into());
3388            }
3389            remaining_capacity -= 1;
3390            references.push(parse_signing_reference(child, xpath_budget)?);
3391            manifest_references += 1;
3392        }
3393        if manifest_references == 0 {
3394            return Err(SigningDigestError::MissingElement {
3395                element: "Reference",
3396            });
3397        }
3398    }
3399    Ok(references)
3400}
3401
3402fn parse_signing_reference(
3403    reference_node: Node<'_, '_>,
3404    xpath_budget: &mut XPathSignatureParseBudget,
3405) -> Result<SigningReference, SigningDigestError> {
3406    let structure = parse_signing_reference_structure(reference_node)?;
3407    let transforms = structure.transforms_node.map_or_else(
3408        || Ok(Vec::new()),
3409        |node| parse_transforms_with_budget(node, xpath_budget),
3410    )?;
3411    let digest_uri = required_algorithm_attr(structure.digest_method_node, "DigestMethod")?;
3412    let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
3413        SigningDigestError::UnsupportedAlgorithm {
3414            uri: digest_uri.to_string(),
3415        }
3416    })?;
3417
3418    Ok(SigningReference {
3419        uri: structure.uri.to_owned(),
3420        origin_node_id: reference_node.id(),
3421        transforms,
3422        digest_method,
3423        digest_value_range: structure.digest_value_node.range(),
3424        digest_value_node_id: structure.digest_value_node.id(),
3425    })
3426}
3427
3428struct SigningReferenceStructure<'a> {
3429    uri: &'a str,
3430    transforms_node: Option<Node<'a, 'a>>,
3431    digest_method_node: Node<'a, 'a>,
3432    digest_value_node: Node<'a, 'a>,
3433}
3434
3435fn parse_signing_reference_structure<'a>(
3436    reference_node: Node<'a, 'a>,
3437) -> Result<SigningReferenceStructure<'a>, SigningDigestError> {
3438    let uri = reference_node.attribute("URI").ok_or_else(|| {
3439        SigningDigestError::InvalidStructure("signing Reference must include URI attribute".into())
3440    })?;
3441    let mut children = element_children(reference_node);
3442    let first = children.next().ok_or(SigningDigestError::MissingElement {
3443        element: "DigestMethod",
3444    })?;
3445    let (transforms_node, digest_method_node) = if first.has_tag_name((XMLDSIG_NS, "Transforms")) {
3446        (
3447            Some(first),
3448            children.next().ok_or(SigningDigestError::MissingElement {
3449                element: "DigestMethod",
3450            })?,
3451        )
3452    } else {
3453        (None, first)
3454    };
3455    verify_ds_element(digest_method_node, "DigestMethod")?;
3456    let digest_value_node = children.next().ok_or(SigningDigestError::MissingElement {
3457        element: "DigestValue",
3458    })?;
3459    verify_ds_element(digest_value_node, "DigestValue")?;
3460    if let Some(unexpected) = children.next() {
3461        return Err(SigningDigestError::InvalidStructure(format!(
3462            "unexpected element <{}> after <DigestValue> in <Reference>",
3463            unexpected.tag_name().name()
3464        )));
3465    }
3466    Ok(SigningReferenceStructure {
3467        uri,
3468        transforms_node,
3469        digest_method_node,
3470        digest_value_node,
3471    })
3472}
3473
3474fn find_required_child<'a>(
3475    parent: Node<'a, 'a>,
3476    child_name: &'static str,
3477) -> Result<Node<'a, 'a>, SigningDigestError> {
3478    parent
3479        .children()
3480        .find(|node| {
3481            node.is_element()
3482                && node.tag_name().name() == child_name
3483                && node.tag_name().namespace() == Some(XMLDSIG_NS)
3484        })
3485        .ok_or(SigningDigestError::MissingElement {
3486            element: child_name,
3487        })
3488}
3489
3490fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
3491    node.children().filter(Node::is_element)
3492}
3493
3494fn verify_ds_element(
3495    node: Node<'_, '_>,
3496    expected_name: &'static str,
3497) -> Result<(), SigningDigestError> {
3498    if !node.is_element() {
3499        return Err(SigningDigestError::InvalidStructure(format!(
3500            "expected element <{expected_name}>, got non-element node"
3501        )));
3502    }
3503    let tag = node.tag_name();
3504    if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
3505        return Err(SigningDigestError::InvalidStructure(format!(
3506            "expected <ds:{expected_name}>, got <{}>",
3507            tag.name()
3508        )));
3509    }
3510    Ok(())
3511}
3512
3513fn required_algorithm_attr<'a>(
3514    node: Node<'a, 'a>,
3515    element_name: &'static str,
3516) -> Result<&'a str, SigningDigestError> {
3517    node.attribute("Algorithm").ok_or_else(|| {
3518        SigningDigestError::InvalidStructure(format!(
3519            "missing Algorithm attribute on <{element_name}>"
3520        ))
3521    })
3522}
3523
3524#[cfg(test)]
3525mod error_conversion_tests {
3526    use super::*;
3527    use crate::policy::PolicyViolation;
3528
3529    struct RejectingSigningKey;
3530
3531    impl SigningKey for RejectingSigningKey {
3532        fn sign(
3533            &self,
3534            _algorithm: SignatureAlgorithm,
3535            _canonical_signed_info: &[u8],
3536        ) -> Result<Vec<u8>, SigningKeyError> {
3537            Err(SigningKeyError::SigningFailed)
3538        }
3539
3540        fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
3541            Err(SigningKeyError::PublicKeyEncodingFailed)
3542        }
3543    }
3544
3545    struct FixedRsaSigningKey;
3546
3547    impl SigningKey for FixedRsaSigningKey {
3548        fn sign(
3549            &self,
3550            _algorithm: SignatureAlgorithm,
3551            _canonical_signed_info: &[u8],
3552        ) -> Result<Vec<u8>, SigningKeyError> {
3553            Ok(vec![0x5a; 256])
3554        }
3555
3556        fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
3557            Ok(SigningPublicKeyInfo::Rsa {
3558                spki_der: Vec::new(),
3559                modulus: vec![0x80; 256],
3560                exponent: vec![1, 0, 1],
3561            })
3562        }
3563    }
3564
3565    #[test]
3566    fn signing_error_promotes_every_policy_failure() {
3567        // All policy refusals use one public pipeline variant regardless of
3568        // which internal signing stage first enforces the immutable snapshot.
3569        let digest = SigningError::from(SigningDigestError::Policy(PolicyViolation::Algorithm {
3570            operation: "signing",
3571            algorithm: "urn:test:digest".into(),
3572        }));
3573        assert!(matches!(digest, SigningError::Policy(_)));
3574
3575        let mutation = SigningError::from(SigningDigestError::XmlMutation(
3576            XmlMutationError::Policy(PolicyViolation::ResourceLimit {
3577                resource: crate::policy::resource_name::XML_DOCUMENT,
3578                maximum: 1,
3579                actual: 2,
3580            }),
3581        ));
3582        assert!(matches!(mutation, SigningError::Policy(_)));
3583    }
3584
3585    #[test]
3586    fn manifest_reparse_consumes_the_signature_wide_xpath_budget() {
3587        // Signing reparses Manifest references after each dependency level.
3588        // Repeated parser/compiler work must consume the original signature
3589        // budget rather than receiving a fresh allowance for every level.
3590        let filter = r#"<xf:XPath xmlns:xf="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</xf:XPath>"#;
3591        let signed_info_transforms = format!(
3592            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>"#,
3593            filter.repeat(64)
3594        );
3595        let signed_info_references = (0..62)
3596            .map(|index| {
3597                let extra = if index == 0 {
3598                    r#"<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>true()</ds:XPath></ds:Transform>"#
3599                } else {
3600                    ""
3601                };
3602                format!(
3603                    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>"##
3604                )
3605            })
3606            .collect::<String>();
3607        let manifest_reference = |id: &str| {
3608            format!(
3609                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>"##
3610            )
3611        };
3612        let xml = format!(
3613            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>"##,
3614            manifest_reference("payload"),
3615            manifest_reference("payload")
3616        );
3617        let policy = crate::policy::SigningPolicy {
3618            manifest_processing: crate::policy::ManifestProcessing::Process,
3619            ..crate::policy::SigningPolicy::default()
3620        };
3621
3622        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3623        let error = fill_reference_digest_values_in_dependency_order(
3624            &mut document,
3625            TransformOptions::default(),
3626            &policy,
3627            crate::provider::default_provider(),
3628            &mut SigningOperationBudgets::default(),
3629            0,
3630            &[],
3631        )
3632        .expect_err("Manifest reparse must not reset the XPath parse budget");
3633
3634        assert!(
3635            matches!(
3636                &error,
3637                SigningDigestError::Transform(TransformError::Policy(
3638                    crate::policy::PolicyViolation::ResourceLimit {
3639                        resource: "XPath expressions",
3640                        ..
3641                    }
3642                ))
3643            ),
3644            "expected the shared XPath budget error, got: {error:?}"
3645        );
3646    }
3647
3648    #[test]
3649    fn dependency_levels_share_the_xml_parse_work_budget() {
3650        // Nested Manifest dependencies require successive digest generations.
3651        // Analysis mutations and every level's validation/commit parses must
3652        // consume one monotonic budget instead of resetting in recursive work.
3653        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>"##;
3654        let policy = crate::policy::SigningPolicy {
3655            manifest_processing: crate::policy::ManifestProcessing::Process,
3656            ..crate::policy::SigningPolicy::default()
3657        };
3658        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3659        let mut budgets = SigningOperationBudgets::from_resources(&policy.resources);
3660
3661        fill_reference_digest_values_in_dependency_order(
3662            &mut document,
3663            TransformOptions::default(),
3664            &policy,
3665            crate::provider::default_provider(),
3666            &mut budgets,
3667            0,
3668            &[],
3669        )
3670        .expect("the default cumulative budget must cover nested dependencies");
3671        let consumed = budgets.transforms.xml_parse_work().consumed();
3672        assert!(
3673            consumed > xml.len().saturating_mul(6),
3674            "analysis and dependency reparses must all be charged"
3675        );
3676
3677        let mut constrained_policy = policy;
3678        constrained_policy.resources.max_xml_parse_work_bytes = consumed - 1;
3679        let mut constrained_document = XmlDocument::parse(xml).expect("fixture must parse");
3680        let mut constrained_budgets =
3681            SigningOperationBudgets::from_resources(&constrained_policy.resources);
3682        let error = fill_reference_digest_values_in_dependency_order(
3683            &mut constrained_document,
3684            TransformOptions::default(),
3685            &constrained_policy,
3686            crate::provider::default_provider(),
3687            &mut constrained_budgets,
3688            0,
3689            &[],
3690        )
3691        .expect_err("one byte below measured work must fail closed");
3692
3693        assert!(matches!(
3694            error,
3695            SigningDigestError::Policy(PolicyViolation::ResourceLimit {
3696                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3697                maximum,
3698                actual,
3699            }) if maximum == consumed - 1 && actual >= consumed
3700        ));
3701    }
3702
3703    #[test]
3704    fn final_signed_info_parse_consumes_the_signing_xpath_budget() {
3705        // One XPath Reference is parsed while discovering references, while
3706        // analyzing dependencies, and while filling its dependency level. The
3707        // final SignedInfo parse must consume the same operation-wide budget.
3708        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>"##;
3709        let mut policy = crate::policy::SigningPolicy::default();
3710        policy.resources.max_xpath_expressions = 3;
3711
3712        let error = SignContext::new(&RejectingSigningKey)
3713            .policy(policy)
3714            .sign_template(xml)
3715            .expect_err("the final SignedInfo parse must not reset the XPath budget");
3716
3717        assert!(
3718            matches!(
3719                error,
3720                SigningError::Policy(PolicyViolation::ResourceLimit {
3721                    resource: crate::policy::resource_name::XPATH_EXPRESSIONS,
3722                    maximum: 3,
3723                    ..
3724                })
3725            ),
3726            "expected the shared XPath parse budget error, got: {error:?}"
3727        );
3728    }
3729
3730    #[test]
3731    fn signing_initial_parse_consumes_the_operation_xml_parse_budget() {
3732        // The input parse and every later retained-document reparse belong to
3733        // one monotonic operation allowance; helpers must not create a fresh
3734        // budget before digest or mutation work begins.
3735        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>"##;
3736        let mut policy = crate::policy::SigningPolicy::default();
3737        policy.resources.max_xml_parse_work_bytes = 0;
3738
3739        let error = SignContext::new(&RejectingSigningKey)
3740            .policy(policy)
3741            .sign_template(xml)
3742            .expect_err("a zero parse-work budget must reject the initial parse");
3743
3744        assert!(matches!(
3745            error,
3746            SigningError::Policy(PolicyViolation::ResourceLimit {
3747                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3748                maximum: 0,
3749                actual,
3750            }) if actual == xml.len()
3751        ));
3752    }
3753
3754    #[test]
3755    fn signing_string_entry_point_enforces_policy_depth() {
3756        // Operation parsing must use the compiled policy depth rather than the
3757        // process-wide hard ceiling, before template discovery or key use.
3758        let mut policy = crate::policy::SigningPolicy::default();
3759        policy.resources.max_xml_depth = 2;
3760        let xml = "<root><child><leaf/></child></root>";
3761
3762        assert!(matches!(
3763            SignContext::new(&RejectingSigningKey)
3764                .policy(policy)
3765                .sign_template(xml),
3766            Err(SigningError::Policy(PolicyViolation::ResourceLimit {
3767                resource: crate::policy::resource_name::XML_DEPTH,
3768                maximum: 2,
3769                actual: 3,
3770            }))
3771        ));
3772    }
3773
3774    #[test]
3775    fn builder_append_reports_policy_depth() {
3776        // The generated template fits as a standalone tree, but its methods
3777        // cross the same depth policy after the Signature is appended.
3778        let mut policy = crate::policy::SigningPolicy::default();
3779        policy.resources.max_xml_depth = 4;
3780        let builder = SignatureBuilder::new(
3781            crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3782            SignatureAlgorithm::RsaSha256,
3783        )
3784        .add_reference(crate::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256).uri(""));
3785
3786        let result = SignContext::new(&FixedRsaSigningKey)
3787            .policy(policy)
3788            .sign_with_builder("<root/>", &builder);
3789        assert!(
3790            matches!(
3791                result,
3792                Err(SigningError::Policy(PolicyViolation::ResourceLimit {
3793                    resource: crate::policy::resource_name::XML_DEPTH,
3794                    maximum: 4,
3795                    actual: 5,
3796                }))
3797            ),
3798            "unexpected builder depth result: {result:?}"
3799        );
3800    }
3801
3802    #[test]
3803    fn owned_signing_staged_copies_preserve_policy_errors() {
3804        // Both retained-document entry points must expose operation policy
3805        // exhaustion directly and leave the caller's generation untouched.
3806        let mut policy = crate::policy::SigningPolicy::default();
3807        policy.resources.max_xml_parse_work_bytes = 0;
3808        let context = SignContext::new(&RejectingSigningKey).policy(policy);
3809
3810        let mut template_document = XmlDocument::parse("<root/>").expect("fixture must parse");
3811        let template_before = template_document.as_xml().to_owned();
3812        let error = context
3813            .sign_document(&mut template_document)
3814            .expect_err("the staged template copy must exhaust the operation budget");
3815        assert!(matches!(
3816            error,
3817            SigningError::Policy(PolicyViolation::ResourceLimit {
3818                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3819                maximum: 0,
3820                actual,
3821            }) if actual == template_before.len()
3822        ));
3823        assert_eq!(template_document.as_xml(), template_before);
3824        assert_eq!(template_document.generation(), 0);
3825
3826        let mut builder_document = XmlDocument::parse("<root/>").expect("fixture must parse");
3827        let builder_before = builder_document.as_xml().to_owned();
3828        let builder = SignatureBuilder::new(
3829            crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3830            SignatureAlgorithm::RsaSha256,
3831        );
3832        let error = context
3833            .sign_document_with_builder(&mut builder_document, &builder)
3834            .expect_err("the staged builder copy must exhaust the operation budget");
3835        assert!(matches!(
3836            error,
3837            SigningError::Policy(PolicyViolation::ResourceLimit {
3838                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3839                maximum: 0,
3840                actual,
3841            }) if actual == builder_before.len()
3842        ));
3843        assert_eq!(builder_document.as_xml(), builder_before);
3844        assert_eq!(builder_document.generation(), 0);
3845    }
3846
3847    #[test]
3848    fn owned_signing_commits_the_validated_stage_without_reparsing() {
3849        // Atomic commit must adopt the already validated staged cell. Charging
3850        // another complete backend parse makes valid maximum-size inputs exceed
3851        // the operation ceiling only because they use the owned entry point.
3852        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>"##;
3853        let policy = crate::policy::SigningPolicy::default();
3854        let context = SignContext::new(&FixedRsaSigningKey).policy(policy.clone());
3855        let source = XmlDocument::parse(xml).expect("fixture must parse");
3856        let mut measured = SigningOperationBudgets::from_resources(&policy.resources);
3857        let mut staged = source
3858            .staged_copy_with_budget(
3859                DocumentParseSettings::from_policy(&policy.xml, &policy.resources),
3860                measured.transforms.xml_parse_work(),
3861            )
3862            .expect("staging must parse");
3863        context
3864            .sign_document_in_place(&mut staged, &mut measured)
3865            .expect("staged signing must succeed");
3866        let exact_stage_work = measured.transforms.xml_parse_work().consumed();
3867
3868        let mut constrained_policy = policy;
3869        constrained_policy.resources.max_xml_parse_work_bytes = exact_stage_work;
3870        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3871        SignContext::new(&FixedRsaSigningKey)
3872            .policy(constrained_policy)
3873            .sign_document(&mut document)
3874            .expect("commit must not parse the validated stage again");
3875
3876        assert_eq!(document.generation(), 1);
3877        assert!(!document.as_xml().contains("<ds:DigestValue/>"));
3878        assert!(!document.as_xml().contains("<ds:SignatureValue/>"));
3879    }
3880
3881    #[test]
3882    fn builder_signing_fits_the_document_to_parse_work_ratio() {
3883        // A builder operation at the configured document ceiling must fit the
3884        // implementation's hard allowance. Generated base64 text and the
3885        // appended generated template need one committed candidate parse each,
3886        // not an untrusted-fragment validation parse plus commit. Differential
3887        // builds meter their comparison backend without reducing this envelope.
3888        let padding = "x".repeat(64 * 1024);
3889        let xml = format!("<root><payload Id=\"payload\"/><padding>{padding}</padding></root>");
3890        let builder = SignatureBuilder::new(
3891            crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3892            SignatureAlgorithm::RsaSha256,
3893        )
3894        .add_reference(
3895            crate::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256).uri("#payload"),
3896        );
3897        let maximum_document_bytes = xml.len() + 4 * 1024;
3898        let mut policy = crate::policy::SigningPolicy::default();
3899        policy.resources.max_xml_document_bytes = maximum_document_bytes;
3900        policy.resources.max_xml_parse_work_bytes =
3901            maximum_document_bytes * crate::hard_limits::XML_PARSE_WORK_PASS_CEILING;
3902
3903        let mut measurement_policy = policy.clone();
3904        measurement_policy.resources.max_xml_parse_work_bytes =
3905            crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING;
3906        let measurement_context =
3907            SignContext::new(&FixedRsaSigningKey).policy(measurement_policy.clone());
3908        let mut measurement_budgets =
3909            SigningOperationBudgets::from_resources(&measurement_policy.resources);
3910        let mut measurement_document = XmlDocument::parse_with_settings_and_budget(
3911            xml.clone(),
3912            DocumentParseSettings::from_policy(
3913                &measurement_policy.xml,
3914                &measurement_policy.resources,
3915            ),
3916            measurement_budgets.transforms.xml_parse_work(),
3917        )
3918        .expect("measurement input must parse");
3919        measurement_context
3920            .sign_document_with_builder_in_place(
3921                &mut measurement_document,
3922                &builder,
3923                &mut measurement_budgets,
3924            )
3925            .expect("measurement signing must succeed");
3926        let consumed = measurement_budgets.transforms.xml_parse_work().consumed();
3927        assert!(
3928            consumed <= maximum_document_bytes * crate::hard_limits::XML_PARSE_WORK_PASS_CEILING,
3929            "builder signing consumed {consumed} bytes for a {maximum_document_bytes}-byte ceiling"
3930        );
3931
3932        let signed = SignContext::new(&FixedRsaSigningKey)
3933            .policy(policy.clone())
3934            .sign_with_builder(&xml, &builder)
3935            .expect("string builder signing must fit the advertised parse-work ratio");
3936        assert!(signed.contains("DigestValue>"));
3937        assert!(signed.contains("SignatureValue>"));
3938
3939        let mut owned = XmlDocument::parse(&xml).expect("fixture must parse");
3940        SignContext::new(&FixedRsaSigningKey)
3941            .policy(policy)
3942            .sign_document_with_builder(&mut owned, &builder)
3943            .expect("owned builder signing must fit the advertised parse-work ratio");
3944        assert!(owned.as_xml().contains("DigestValue>"));
3945        assert!(owned.as_xml().contains("SignatureValue>"));
3946    }
3947
3948    #[test]
3949    fn dtd_capable_staged_copy_charges_both_parser_passes() {
3950        // DTD-capable documents run a provenance parse before the retained
3951        // document parse. Both passes belong to the signing operation budget.
3952        let xml = "<root/>";
3953        let mut parsing_policy = crate::policy::SigningPolicy::default();
3954        parsing_policy.xml.allow_internal_dtd = true;
3955        let document = XmlDocument::parse_with_policy(xml, &parsing_policy)
3956            .expect("the fixture must retain DTD-capable parse settings");
3957
3958        parsing_policy.resources.max_xml_parse_work_bytes = xml.len();
3959        let budget = XmlParseWorkBudget::from_resources(&parsing_policy.resources);
3960        assert!(matches!(
3961            document.staged_copy_with_budget(DocumentParseSettings::default(), &budget),
3962            Err(XmlDocumentError::Policy(PolicyViolation::ResourceLimit {
3963                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3964                maximum,
3965                actual,
3966            })) if maximum == xml.len() && actual == xml.len() * 2
3967        ));
3968    }
3969
3970    #[test]
3971    fn owned_signing_mappers_preserve_document_size_policy_errors() {
3972        // Template and digest mutations share one resource-error contract even
3973        // though their surrounding signing error types differ.
3974        let maximum = 8;
3975        let actual = 9;
3976        assert!(matches!(
3977            map_owned_document_mutation_error(XmlDocumentError::DocumentTooLarge {
3978                maximum,
3979                actual,
3980            }),
3981            SigningError::Policy(PolicyViolation::ResourceLimit {
3982                resource: crate::policy::resource_name::XML_DOCUMENT,
3983                maximum: 8,
3984                actual: 9,
3985            })
3986        ));
3987        assert!(matches!(
3988            map_owned_document_digest_mutation_error(XmlDocumentError::DocumentTooLarge {
3989                maximum,
3990                actual,
3991            }),
3992            SigningDigestError::Policy(PolicyViolation::ResourceLimit {
3993                resource: crate::policy::resource_name::XML_DOCUMENT,
3994                maximum: 8,
3995                actual: 9,
3996            })
3997        ));
3998    }
3999
4000    #[test]
4001    fn signing_rejects_xpath_control_dependencies_on_digest_values() {
4002        // The second Reference excludes DigestValue nodes from its output but
4003        // reads the first DigestValue to decide whether payload remains. Filling
4004        // both references in one level would therefore invalidate the result.
4005        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>"##;
4006
4007        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4008        let error = fill_reference_digest_values_in_dependency_order(
4009            &mut document,
4010            TransformOptions::default(),
4011            &crate::policy::SigningPolicy::default(),
4012            crate::provider::default_provider(),
4013            &mut SigningOperationBudgets::default(),
4014            0,
4015            &[],
4016        )
4017        .expect_err("mutable XPath control dependencies must fail closed");
4018
4019        assert!(
4020            matches!(
4021                &error,
4022                SigningDigestError::InvalidStructure(message)
4023                    if message.contains("dependency cycle")
4024            ),
4025            "expected a dependency-cycle rejection, got: {error:?}"
4026        );
4027    }
4028
4029    #[test]
4030    fn signing_allows_payload_local_xpath_value_predicates() {
4031        // Attribute comparisons read payload metadata, not mutable values in a
4032        // disjoint Signature subtree. They must not manufacture a self-cycle.
4033        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>"##;
4034
4035        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4036        fill_reference_digest_values_in_dependency_order(
4037            &mut document,
4038            TransformOptions::default(),
4039            &crate::policy::SigningPolicy::default(),
4040            crate::provider::default_provider(),
4041            &mut SigningOperationBudgets::default(),
4042            0,
4043            &[],
4044        )
4045        .expect("payload-local XPath predicates must not depend on Signature values");
4046
4047        assert_ne!(document.as_xml(), xml);
4048    }
4049
4050    #[test]
4051    fn signing_rejects_references_that_retain_signature_value() {
4052        // SignatureValue is filled after every Reference digest. A Reference
4053        // retaining that node is therefore an unavoidable signing cycle even
4054        // when all DigestValue nodes are excluded from the resulting node-set.
4055        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>"##;
4056
4057        let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4058        let error = fill_reference_digest_values_in_dependency_order(
4059            &mut document,
4060            TransformOptions::default(),
4061            &crate::policy::SigningPolicy::default(),
4062            crate::provider::default_provider(),
4063            &mut SigningOperationBudgets::default(),
4064            0,
4065            &[],
4066        )
4067        .expect_err("a mutable SignatureValue dependency must fail before signing");
4068
4069        assert!(
4070            matches!(
4071                &error,
4072                SigningDigestError::InvalidStructure(message)
4073                    if message.contains("SignatureValue") && message.contains("cycle")
4074            ),
4075            "expected a SignatureValue dependency-cycle rejection, got: {error:?}"
4076        );
4077    }
4078
4079    #[test]
4080    fn second_edition_c14n_materialization_handles_both_reference_shapes() {
4081        // Templates may omit <Transforms> entirely or serialize an empty
4082        // prefixed element. Both forms must become one valid explicit chain.
4083        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>"##;
4084        let document = XmlDocument::parse(xml).expect("fixture must parse");
4085        let candidate = materialize_second_edition_c14n11_candidate(
4086            &document,
4087            0,
4088            &crate::policy::SigningPolicy::default(),
4089            false,
4090        )
4091        .expect("materialization planning must succeed")
4092        .expect("both node-set chains require explicit canonicalization");
4093
4094        assert_eq!(
4095            candidate
4096                .matches(SECOND_EDITION_GENERATION_C14N_URI)
4097                .count(),
4098            3
4099        );
4100        assert!(candidate.contains("<ds:Transforms><ds:Transform"));
4101        XmlDocument::parse(candidate).expect("materialized candidate must remain well-formed");
4102    }
4103
4104    #[test]
4105    fn second_edition_c14n_materialization_is_idempotent_for_binary_chains() {
4106        // An explicit canonicalization already produces octets; planning must
4107        // avoid both duplicate transforms and a no-op document generation.
4108        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>"##;
4109        let document = XmlDocument::parse(xml).expect("fixture must parse");
4110        let candidate = materialize_second_edition_c14n11_candidate(
4111            &document,
4112            0,
4113            &crate::policy::SigningPolicy::default(),
4114            false,
4115        )
4116        .expect("materialization planning must succeed");
4117
4118        assert!(candidate.is_none());
4119    }
4120}