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