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