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