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