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