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