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 base64::Engine;
9use p256::ecdsa::{Signature as P256Signature, SigningKey as P256SigningKey};
10use p256::pkcs8::{DecodePrivateKey, EncodePublicKey};
11use p384::ecdsa::{Signature as P384Signature, SigningKey as P384SigningKey};
12use roxmltree::{Document, Node};
13use rsa::RsaPrivateKey;
14use rsa::pkcs1v15::Signature as RsaPkcs1v15Signature;
15use rsa::pkcs1v15::SigningKey as RsaPkcs1v15SigningKey;
16use rsa::signature::{RandomizedSigner, SignatureEncoding};
17use rsa::traits::PublicKeyParts;
18use sha2::{Sha256, Sha384, Sha512};
19use signature::hazmat::PrehashSigner;
20use std::collections::HashSet;
21use x509_parser::prelude::FromDer;
22
23use crate::c14n::{canonicalize_bounded_with_xml_base_budget, is_output_limit_error};
24
25use super::builder::{SignatureBuilder, SignatureBuilderError};
26use super::digest::DigestAlgorithm;
27use super::mutation::{
28    XmlMutationError, append_signature_to_root_with_options, fill_key_info_with_options,
29    fill_signature_value_with_options, fill_signed_info_digest_values,
30    fill_signed_info_digest_values_with_options,
31};
32use super::parse::{
33    MAX_REFERENCES_PER_SIGNATURE, SignatureAlgorithm, XMLDSIG_NS, parse_signed_info,
34};
35use super::transforms::{
36    DEFAULT_IMPLICIT_C14N_URI, Transform, TransformExecutionBudget, TransformOptions,
37    XPathHereSemantics, XPathSignatureParseBudget, execute_transforms_with_options_and_budget,
38    parse_transforms_with_budget, transform_chain_produces_binary,
39};
40use super::types::TransformError;
41use super::uri::UriReferenceResolver;
42
43/// Result for one computed signing-template reference digest.
44#[derive(Debug, Clone, PartialEq, Eq)]
45#[must_use = "use the computed digest value to fill the corresponding <DigestValue>"]
46pub struct ComputedReferenceDigest {
47    /// Zero-based reference index in `<SignedInfo>` document order.
48    pub index: usize,
49    /// Reference URI used for same-document dereference.
50    pub uri: String,
51    /// Digest algorithm declared by `<DigestMethod>`.
52    pub digest_method: DigestAlgorithm,
53    /// Base64-encoded digest value ready for `<DigestValue>`.
54    pub digest_value: String,
55}
56
57/// Errors returned by the XMLDSig signing digest pass.
58#[derive(Debug, thiserror::Error)]
59pub enum SigningDigestError {
60    /// The selected provider could not compute a reference digest.
61    #[error("cryptographic provider error: {0}")]
62    Provider(#[from] crate::provider::ProviderError),
63
64    /// The compiled signing policy rejected an operation input.
65    #[error("signing policy violation: {0}")]
66    Policy(#[from] crate::policy::PolicyViolation),
67
68    /// The input XML document is not well-formed.
69    #[error("XML parse error: {0}")]
70    XmlParse(#[from] roxmltree::Error),
71
72    /// Required XMLDSig element is missing.
73    #[error("missing required element: <{element}>")]
74    MissingElement {
75        /// Required element name.
76        element: &'static str,
77    },
78
79    /// XMLDSig template structure is invalid.
80    #[error("invalid signing template: {0}")]
81    InvalidStructure(String),
82
83    /// Digest algorithm URI is not supported.
84    #[error("unsupported digest algorithm: {uri}")]
85    UnsupportedAlgorithm {
86        /// Unrecognized algorithm URI.
87        uri: String,
88    },
89
90    /// Digest algorithm is supported for verification but disabled for signing.
91    #[error("digest algorithm is disabled for signing: {uri}")]
92    SigningAlgorithmDisabled {
93        /// Algorithm URI rejected for new signatures.
94        uri: &'static str,
95    },
96
97    /// URI dereference or transform execution failed.
98    #[error("reference processing error: {0}")]
99    Transform(#[from] TransformError),
100
101    /// Writing computed digest values back into XML failed.
102    #[error("XML mutation error: {0}")]
103    XmlMutation(#[from] XmlMutationError),
104}
105
106/// Errors returned by the full XMLDSig signing pipeline.
107#[derive(Debug, thiserror::Error)]
108pub enum SigningError {
109    /// The compiled signing policy rejected an operation input.
110    #[error("signing policy violation: {0}")]
111    Policy(#[from] crate::policy::PolicyViolation),
112
113    /// Reference digest computation failed.
114    #[error("signing digest pass failed: {0}")]
115    Digest(#[from] SigningDigestError),
116
117    /// Parsing the digest-filled `<SignedInfo>` failed.
118    #[error("failed to parse SignedInfo after digest fill: {0}")]
119    ParseSignedInfo(#[from] super::parse::ParseError),
120
121    /// SignedInfo canonicalization failed.
122    #[error("SignedInfo canonicalization failed: {0}")]
123    Canonicalization(#[from] crate::c14n::C14nError),
124
125    /// Signing key preparation or signing failed.
126    #[error("signing key error: {0}")]
127    Key(#[from] SigningKeyError),
128
129    /// A signing provider returned bytes that cannot encode this key's signature.
130    #[error("signature output must be {expected} bytes, got {actual}")]
131    InvalidSignatureOutputLength {
132        /// Exact XMLDSig wire length implied by the signing public key.
133        expected: usize,
134        /// Actual provider output length.
135        actual: usize,
136    },
137
138    /// Writing `<SignatureValue>` failed.
139    #[error("XML mutation error: {0}")]
140    XmlMutation(#[from] XmlMutationError),
141
142    /// Writing `<KeyInfo>` failed.
143    #[error("KeyInfo writer error: {0}")]
144    KeyInfo(#[from] KeyInfoWriteError),
145
146    /// Signature template generation failed.
147    #[error("signature template error: {0}")]
148    Template(#[from] SignatureBuilderError),
149}
150
151/// Errors while parsing or using XMLDSig signing keys.
152#[derive(Debug, thiserror::Error)]
153#[non_exhaustive]
154pub enum SigningKeyError {
155    /// The selected provider cannot execute the requested operation.
156    #[error("cryptographic provider error: {0}")]
157    Provider(#[from] crate::provider::ProviderError),
158
159    /// PEM input could not be parsed.
160    #[error("invalid PEM private key")]
161    InvalidKeyPem,
162
163    /// PEM block was not an unencrypted PKCS#8 private key.
164    #[error("invalid key format: expected PRIVATE KEY PEM, got {label}")]
165    InvalidKeyFormat {
166        /// Actual PEM label.
167        label: String,
168    },
169
170    /// DER bytes could not be decoded for the requested key type.
171    #[error("invalid PKCS#8 private key DER")]
172    InvalidKeyDer,
173
174    /// The signing key cannot produce the requested XMLDSig algorithm.
175    #[error("signing key does not support algorithm: {uri}")]
176    UnsupportedAlgorithm {
177        /// XMLDSig signature algorithm URI.
178        uri: String,
179    },
180
181    /// The private-key signing operation failed.
182    #[error("private-key signing operation failed")]
183    SigningFailed,
184
185    /// Public-key encoding failed for a supported signing key.
186    #[error("failed to encode signing public key as SPKI DER")]
187    PublicKeyEncodingFailed,
188
189    /// Public-key metadata cannot determine the XMLDSig signature framing.
190    #[error("invalid signing public-key metadata")]
191    InvalidPublicKeyInfo,
192}
193
194/// Public key material corresponding to a private XMLDSig signing key.
195#[derive(Debug, Clone, PartialEq, Eq)]
196#[non_exhaustive]
197pub enum SigningPublicKeyInfo {
198    /// RSA public key with DER SubjectPublicKeyInfo and normalized parameters.
199    Rsa {
200        /// DER-encoded SubjectPublicKeyInfo bytes.
201        spki_der: Vec<u8>,
202        /// Unsigned big-endian RSA modulus (`n`), normalized without leading zeroes.
203        modulus: Vec<u8>,
204        /// Unsigned big-endian RSA public exponent (`e`), normalized without leading zeroes.
205        exponent: Vec<u8>,
206    },
207    /// EC public key with DER SubjectPublicKeyInfo and XMLDSig 1.1 KeyValue data.
208    Ec {
209        /// DER-encoded SubjectPublicKeyInfo bytes.
210        spki_der: Vec<u8>,
211        /// Bare named-curve OID, without the XMLDSig `urn:oid:` prefix.
212        curve_oid: &'static str,
213        /// Uncompressed SEC1 point (`0x04 || x || y`).
214        public_key: Vec<u8>,
215    },
216}
217
218impl SigningPublicKeyInfo {
219    /// Return DER-encoded SubjectPublicKeyInfo bytes for this public key.
220    #[must_use]
221    pub fn spki_der(&self) -> &[u8] {
222        match self {
223            Self::Rsa { spki_der, .. } | Self::Ec { spki_der, .. } => spki_der,
224        }
225    }
226}
227
228fn expected_signature_output_len(
229    key: &dyn SigningKey,
230    algorithm: SignatureAlgorithm,
231    policy: &crate::policy::SigningPolicy,
232) -> Result<usize, SigningError> {
233    let public_key = key.public_key_info()?;
234    let expected = match (algorithm, public_key) {
235        (
236            SignatureAlgorithm::RsaSha1
237            | SignatureAlgorithm::RsaSha256
238            | SignatureAlgorithm::RsaSha384
239            | SignatureAlgorithm::RsaSha512,
240            SigningPublicKeyInfo::Rsa {
241                modulus, exponent, ..
242            },
243        ) => policy
244            .rsa_keys
245            .validate_components("signing", &modulus, &exponent)?,
246        (
247            SignatureAlgorithm::EcdsaSha256 | SignatureAlgorithm::EcdsaSha384,
248            SigningPublicKeyInfo::Ec { public_key, .. },
249        ) if public_key.first() == Some(&0x04)
250            && public_key.len() > 1
251            && (public_key.len() - 1).is_multiple_of(2) =>
252        {
253            // XMLDSig serializes ECDSA as fixed-width r || s. An uncompressed
254            // SEC1 public point is 0x04 || x || y with the same field width.
255            public_key.len() - 1
256        }
257        (
258            SignatureAlgorithm::RsaSha1
259            | SignatureAlgorithm::RsaSha256
260            | SignatureAlgorithm::RsaSha384
261            | SignatureAlgorithm::RsaSha512,
262            SigningPublicKeyInfo::Ec { .. },
263        )
264        | (
265            SignatureAlgorithm::EcdsaSha256 | SignatureAlgorithm::EcdsaSha384,
266            SigningPublicKeyInfo::Rsa { .. },
267        ) => {
268            return Err(SigningKeyError::UnsupportedAlgorithm {
269                uri: algorithm.uri().to_owned(),
270            }
271            .into());
272        }
273        _ => return Err(SigningKeyError::InvalidPublicKeyInfo.into()),
274    };
275    Ok(expected)
276}
277
278fn validate_signature_output(expected: usize, signature: &[u8]) -> Result<(), SigningError> {
279    if signature.len() != expected {
280        return Err(SigningError::InvalidSignatureOutputLength {
281            expected,
282            actual: signature.len(),
283        });
284    }
285    Ok(())
286}
287
288/// Private key abstraction used by [`SignContext`].
289pub trait SigningKey {
290    /// Sign canonicalized `<SignedInfo>` bytes for the declared XMLDSig method.
291    fn sign(
292        &self,
293        algorithm: SignatureAlgorithm,
294        canonical_signed_info: &[u8],
295    ) -> Result<Vec<u8>, SigningKeyError>;
296
297    /// Sign while sourcing any primitive randomness from the selected provider.
298    ///
299    /// Deterministic or externally managed keys can rely on this default. Keys
300    /// whose primitive uses randomness, including RSA blinding, must override it.
301    fn sign_with_provider(
302        &self,
303        provider: &dyn crate::provider::CryptoProvider,
304        algorithm: SignatureAlgorithm,
305        canonical_signed_info: &[u8],
306    ) -> Result<Vec<u8>, SigningKeyError> {
307        let _ = provider;
308        self.sign(algorithm, canonical_signed_info)
309    }
310
311    /// Return structured public key material corresponding to this signing key.
312    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError>;
313}
314
315/// Writes signing key metadata into a template `<KeyInfo>` element.
316pub trait KeyInfoWriter {
317    /// Return XML child content for the direct `<Signature>/<KeyInfo>` element.
318    fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError>;
319}
320
321/// Errors while preparing XMLDSig signing `<KeyInfo>` output.
322#[derive(Debug, thiserror::Error)]
323#[non_exhaustive]
324pub enum KeyInfoWriteError {
325    /// PEM input could not be parsed.
326    #[error("invalid PEM certificate")]
327    InvalidCertificatePem,
328
329    /// PEM block was not an X.509 certificate.
330    #[error("invalid certificate format: expected CERTIFICATE PEM, got {label}")]
331    InvalidCertificateFormat {
332        /// Actual PEM label.
333        label: String,
334    },
335
336    /// DER bytes could not be decoded as one complete X.509 certificate.
337    #[error("invalid X.509 certificate DER")]
338    InvalidCertificateDer,
339
340    /// The signing key could not expose public-key material for validation.
341    #[error("signing key public-key extraction failed: {0}")]
342    SigningKey(#[from] SigningKeyError),
343
344    /// The configured certificate does not contain the signing key's public key.
345    #[error("X.509 certificate public key does not match signing key")]
346    CertificateKeyMismatch,
347}
348
349/// `<KeyInfo>` writer that embeds one DER X.509 certificate.
350pub struct X509CertificateKeyInfoWriter {
351    certificate_der: Vec<u8>,
352}
353
354impl X509CertificateKeyInfoWriter {
355    /// Parse a PEM `CERTIFICATE` block for XMLDSig `<X509Certificate>` output.
356    pub fn from_pem(certificate_pem: &str) -> Result<Self, KeyInfoWriteError> {
357        let (rest, pem) = x509_parser::pem::parse_x509_pem(certificate_pem.as_bytes())
358            .map_err(|_| KeyInfoWriteError::InvalidCertificatePem)?;
359        if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
360            return Err(KeyInfoWriteError::InvalidCertificatePem);
361        }
362        if pem.label != "CERTIFICATE" {
363            return Err(KeyInfoWriteError::InvalidCertificateFormat { label: pem.label });
364        }
365        Self::from_der(&pem.contents)
366    }
367
368    /// Validate and store DER certificate bytes for XMLDSig `<X509Certificate>` output.
369    pub fn from_der(certificate_der: &[u8]) -> Result<Self, KeyInfoWriteError> {
370        let (rest, _) = x509_parser::certificate::X509Certificate::from_der(certificate_der)
371            .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
372        if !rest.is_empty() {
373            return Err(KeyInfoWriteError::InvalidCertificateDer);
374        }
375        Ok(Self {
376            certificate_der: certificate_der.to_vec(),
377        })
378    }
379}
380
381impl KeyInfoWriter for X509CertificateKeyInfoWriter {
382    fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
383        let (rest, certificate) =
384            x509_parser::certificate::X509Certificate::from_der(&self.certificate_der)
385                .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
386        if !rest.is_empty() {
387            return Err(KeyInfoWriteError::InvalidCertificateDer);
388        }
389        let signing_public_key = signing_key.public_key_info()?;
390        if certificate.public_key().raw != signing_public_key.spki_der() {
391            return Err(KeyInfoWriteError::CertificateKeyMismatch);
392        }
393
394        let certificate_b64 =
395            base64::engine::general_purpose::STANDARD.encode(&self.certificate_der);
396        Ok(format!(
397            "<X509Data xmlns=\"{XMLDSIG_NS}\"><X509Certificate>{certificate_b64}</X509Certificate></X509Data>"
398        ))
399    }
400}
401
402/// RSA PKCS#1 v1.5 private key for XMLDSig signing.
403pub struct RsaSigningKey {
404    key: RsaPrivateKey,
405}
406
407impl RsaSigningKey {
408    /// Parse an unencrypted PKCS#8 `PRIVATE KEY` PEM block.
409    pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
410        let private_key_der = parse_private_key_pem(private_key_pem)?;
411        Self::from_pkcs8_der(&private_key_der)
412    }
413
414    /// Parse unencrypted PKCS#8 private key DER.
415    pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
416        let key = RsaPrivateKey::from_pkcs8_der(private_key_der)
417            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
418        Ok(Self { key })
419    }
420}
421
422impl SigningKey for RsaSigningKey {
423    fn sign(
424        &self,
425        algorithm: SignatureAlgorithm,
426        canonical_signed_info: &[u8],
427    ) -> Result<Vec<u8>, SigningKeyError> {
428        self.sign_with_provider(
429            crate::provider::default_provider(),
430            algorithm,
431            canonical_signed_info,
432        )
433    }
434
435    fn sign_with_provider(
436        &self,
437        provider: &dyn crate::provider::CryptoProvider,
438        algorithm: SignatureAlgorithm,
439        canonical_signed_info: &[u8],
440    ) -> Result<Vec<u8>, SigningKeyError> {
441        match algorithm {
442            SignatureAlgorithm::RsaSha256 => sign_rsa_pkcs1v15_with_rng(
443                provider,
444                RsaPkcs1v15SigningKey::<Sha256>::new(self.key.clone()),
445                canonical_signed_info,
446            ),
447            SignatureAlgorithm::RsaSha384 => sign_rsa_pkcs1v15_with_rng(
448                provider,
449                RsaPkcs1v15SigningKey::<Sha384>::new(self.key.clone()),
450                canonical_signed_info,
451            ),
452            SignatureAlgorithm::RsaSha512 => sign_rsa_pkcs1v15_with_rng(
453                provider,
454                RsaPkcs1v15SigningKey::<Sha512>::new(self.key.clone()),
455                canonical_signed_info,
456            ),
457            _ => Err(SigningKeyError::UnsupportedAlgorithm {
458                uri: algorithm.uri().to_string(),
459            }),
460        }
461    }
462
463    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
464        let public_key = self.key.to_public_key();
465        let spki_der = public_key
466            .to_public_key_der()
467            .map(|doc| doc.as_bytes().to_vec())
468            .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
469        Ok(SigningPublicKeyInfo::Rsa {
470            spki_der,
471            modulus: public_key.n().to_be_bytes_trimmed_vartime().into_vec(),
472            exponent: public_key.e().to_be_bytes_trimmed_vartime().into_vec(),
473        })
474    }
475}
476
477fn sign_rsa_pkcs1v15_with_rng(
478    provider: &dyn crate::provider::CryptoProvider,
479    key: impl RandomizedSigner<RsaPkcs1v15Signature>,
480    canonical_signed_info: &[u8],
481) -> Result<Vec<u8>, SigningKeyError> {
482    let mut rng = crate::provider::ProviderRng(provider);
483    let signature = key
484        .try_sign_with_rng(&mut rng, canonical_signed_info)
485        .map_err(|_| SigningKeyError::SigningFailed)?;
486    Ok(signature.to_vec())
487}
488
489/// ECDSA P-256 private key for XMLDSig signing.
490pub struct EcdsaP256SigningKey {
491    key: P256SigningKey,
492}
493
494impl EcdsaP256SigningKey {
495    /// Parse an unencrypted PKCS#8 `PRIVATE KEY` PEM block.
496    pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
497        let private_key_der = parse_private_key_pem(private_key_pem)?;
498        Self::from_pkcs8_der(&private_key_der)
499    }
500
501    /// Parse unencrypted PKCS#8 private key DER.
502    pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
503        let key = P256SigningKey::from_pkcs8_der(private_key_der)
504            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
505        Ok(Self { key })
506    }
507}
508
509impl SigningKey for EcdsaP256SigningKey {
510    fn sign(
511        &self,
512        algorithm: SignatureAlgorithm,
513        canonical_signed_info: &[u8],
514    ) -> Result<Vec<u8>, SigningKeyError> {
515        self.sign_with_provider(
516            crate::provider::default_provider(),
517            algorithm,
518            canonical_signed_info,
519        )
520    }
521
522    fn sign_with_provider(
523        &self,
524        provider: &dyn crate::provider::CryptoProvider,
525        algorithm: SignatureAlgorithm,
526        canonical_signed_info: &[u8],
527    ) -> Result<Vec<u8>, SigningKeyError> {
528        let digest_algorithm = match algorithm {
529            SignatureAlgorithm::EcdsaSha256 => DigestAlgorithm::Sha256,
530            SignatureAlgorithm::EcdsaSha384 => DigestAlgorithm::Sha384,
531            _ => {
532                return Err(SigningKeyError::UnsupportedAlgorithm {
533                    uri: algorithm.uri().to_string(),
534                });
535            }
536        };
537        let prehash =
538            super::compute_digest_with_provider(provider, digest_algorithm, canonical_signed_info)?;
539        let signature: P256Signature = self
540            .key
541            .sign_prehash(&prehash)
542            .map_err(|_| SigningKeyError::SigningFailed)?;
543        Ok(signature.to_bytes().to_vec())
544    }
545
546    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
547        let verifying_key = self.key.verifying_key();
548        let spki_der = verifying_key
549            .to_public_key_der()
550            .map(|doc| doc.as_bytes().to_vec())
551            .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
552        Ok(SigningPublicKeyInfo::Ec {
553            spki_der,
554            curve_oid: "1.2.840.10045.3.1.7",
555            public_key: verifying_key.to_sec1_point(false).as_bytes().to_vec(),
556        })
557    }
558}
559
560/// ECDSA P-384 private key for XMLDSig signing.
561pub struct EcdsaP384SigningKey {
562    key: P384SigningKey,
563}
564
565impl EcdsaP384SigningKey {
566    /// Parse an unencrypted PKCS#8 `PRIVATE KEY` PEM block.
567    pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
568        let private_key_der = parse_private_key_pem(private_key_pem)?;
569        Self::from_pkcs8_der(&private_key_der)
570    }
571
572    /// Parse unencrypted PKCS#8 private key DER.
573    pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
574        let key = P384SigningKey::from_pkcs8_der(private_key_der)
575            .map_err(|_| SigningKeyError::InvalidKeyDer)?;
576        Ok(Self { key })
577    }
578}
579
580impl SigningKey for EcdsaP384SigningKey {
581    fn sign(
582        &self,
583        algorithm: SignatureAlgorithm,
584        canonical_signed_info: &[u8],
585    ) -> Result<Vec<u8>, SigningKeyError> {
586        self.sign_with_provider(
587            crate::provider::default_provider(),
588            algorithm,
589            canonical_signed_info,
590        )
591    }
592
593    fn sign_with_provider(
594        &self,
595        provider: &dyn crate::provider::CryptoProvider,
596        algorithm: SignatureAlgorithm,
597        canonical_signed_info: &[u8],
598    ) -> Result<Vec<u8>, SigningKeyError> {
599        let digest_algorithm = match algorithm {
600            SignatureAlgorithm::EcdsaSha256 => DigestAlgorithm::Sha256,
601            SignatureAlgorithm::EcdsaSha384 => DigestAlgorithm::Sha384,
602            _ => {
603                return Err(SigningKeyError::UnsupportedAlgorithm {
604                    uri: algorithm.uri().to_string(),
605                });
606            }
607        };
608        let prehash =
609            super::compute_digest_with_provider(provider, digest_algorithm, canonical_signed_info)?;
610        let signature: P384Signature = self
611            .key
612            .sign_prehash(&prehash)
613            .map_err(|_| SigningKeyError::SigningFailed)?;
614        Ok(signature.to_bytes().to_vec())
615    }
616
617    fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
618        let verifying_key = self.key.verifying_key();
619        let spki_der = verifying_key
620            .to_public_key_der()
621            .map(|doc| doc.as_bytes().to_vec())
622            .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
623        Ok(SigningPublicKeyInfo::Ec {
624            spki_der,
625            curve_oid: "1.3.132.0.34",
626            public_key: verifying_key.to_sec1_point(false).as_bytes().to_vec(),
627        })
628    }
629}
630
631/// XMLDSig signing context.
632pub struct SignContext<'a> {
633    signing_key: &'a dyn SigningKey,
634    key_info_writer: Option<&'a dyn KeyInfoWriter>,
635    policy: crate::policy::SigningPolicy,
636    provider: &'a dyn crate::provider::CryptoProvider,
637}
638
639impl<'a> SignContext<'a> {
640    /// Create a signing context using the supplied private key.
641    pub fn new(signing_key: &'a dyn SigningKey) -> Self {
642        Self {
643            signing_key,
644            key_info_writer: None,
645            policy: crate::policy::SigningPolicy::default(),
646            provider: crate::provider::default_provider(),
647        }
648    }
649
650    /// Replace the complete immutable signing policy snapshot.
651    #[must_use]
652    pub fn policy(mut self, policy: crate::policy::SigningPolicy) -> Self {
653        self.policy = policy;
654        self
655    }
656
657    /// Select the cryptographic provider for digest and randomness operations.
658    #[must_use]
659    pub fn provider(mut self, provider: &'a dyn crate::provider::CryptoProvider) -> Self {
660        self.provider = provider;
661        self
662    }
663
664    /// Configure signing to populate the direct `<Signature>/<KeyInfo>` placeholder.
665    #[must_use]
666    pub fn key_info_writer(mut self, writer: &'a dyn KeyInfoWriter) -> Self {
667        self.key_info_writer = Some(writer);
668        self
669    }
670
671    /// Select the node returned by XPath's `here()` extension function.
672    ///
673    /// The default follows XMLDSig and returns the `<XPath>` parameter.
674    /// [`XPathHereSemantics::XmlSecLegacy`] is available only for producing
675    /// signatures compatible with libxmlsec1's `<Transform>` interpretation.
676    #[must_use]
677    pub fn xpath_here_semantics(mut self, semantics: XPathHereSemantics) -> Self {
678        self.policy.xpath_here_semantics = semantics;
679        self
680    }
681
682    /// Sign XML that already contains a `<Signature>` template.
683    ///
684    /// The template must include empty `<DigestValue>` and `<SignatureValue>`
685    /// targets. The pipeline fills reference digests, reparses the result,
686    /// canonicalizes `<SignedInfo>`, signs those canonical bytes, and fills the
687    /// base64 `<SignatureValue>`.
688    pub fn sign_template(&self, xml: &str) -> Result<String, SigningError> {
689        self.policy.validate()?;
690        self.policy.resources.validate_xml_document_len(xml.len())?;
691        let execution_budget = TransformExecutionBudget::from_resources(&self.policy.resources);
692        let transform_options = TransformOptions::default()
693            .allow_internal_dtd(self.policy.xml.allow_internal_dtd)
694            .xpath_here_semantics(self.policy.xpath_here_semantics);
695        let with_digests = fill_reference_digest_values_with_options(
696            xml,
697            transform_options,
698            Some(&self.policy),
699            self.provider,
700            &execution_budget,
701        )?;
702        self.policy
703            .resources
704            .validate_xml_document_len(with_digests.len())?;
705        let (algorithm, canonical_signed_info) =
706            canonicalize_signed_info(&with_digests, &self.policy, &execution_budget)?;
707        execution_budget
708            .charge_c14n_output(canonical_signed_info.len())
709            .map_err(SigningDigestError::Transform)?;
710        if !algorithm.signing_allowed()
711            || self
712                .policy
713                .signature_algorithms
714                .as_ref()
715                .is_some_and(|allowed| !allowed.contains(&algorithm))
716        {
717            return Err(crate::policy::PolicyViolation::Algorithm {
718                operation: "signing",
719                algorithm: algorithm.uri().to_string(),
720            }
721            .into());
722        }
723        let expected_signature_len =
724            expected_signature_output_len(self.signing_key, algorithm, &self.policy)?;
725        let signature_value =
726            self.provider
727                .sign(self.signing_key, algorithm, &canonical_signed_info)?;
728        validate_signature_output(expected_signature_len, &signature_value)?;
729        let signature_b64 = base64::engine::general_purpose::STANDARD.encode(signature_value);
730        let signed =
731            fill_signature_value_with_options(&with_digests, &signature_b64, Some(&self.policy))?;
732        self.policy
733            .resources
734            .validate_xml_document_len(signed.len())?;
735        if let Some(writer) = self.key_info_writer {
736            let key_info_content = writer.write_key_info(self.signing_key)?;
737            let signed =
738                fill_key_info_with_options(&signed, &key_info_content, Some(&self.policy))?;
739            self.policy
740                .resources
741                .validate_xml_document_len(signed.len())?;
742            Ok(signed)
743        } else {
744            Ok(signed)
745        }
746    }
747
748    /// Build a signature template, append it to the source root, then sign it.
749    pub fn sign_with_builder(
750        &self,
751        xml: &str,
752        builder: &SignatureBuilder,
753    ) -> Result<String, SigningError> {
754        self.policy.validate()?;
755        self.policy.resources.validate_xml_document_len(xml.len())?;
756        let template = builder.build_template()?;
757        let templated = append_signature_to_root_with_options(xml, &template, Some(&self.policy))?;
758        self.sign_template(&templated)
759    }
760}
761
762#[derive(Debug)]
763struct SigningReference {
764    uri: String,
765    transforms: Vec<Transform>,
766    digest_method: DigestAlgorithm,
767}
768
769/// Compute base64 digest values for every `<Reference>` in the signing template.
770///
771/// References are processed in `<SignedInfo>` document order under the last
772/// XMLDSig `<Signature>` element. `sign_with_builder()` appends a new template
773/// at the end of the source root, so older signatures in an already-signed
774/// document must not become the signing target.
775pub fn compute_reference_digest_values(
776    xml: &str,
777) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
778    let execution_budget = TransformExecutionBudget::default();
779    compute_reference_digest_values_with_options(
780        xml,
781        TransformOptions::default(),
782        None,
783        crate::provider::default_provider(),
784        &execution_budget,
785    )
786}
787
788fn compute_reference_digest_values_with_options(
789    xml: &str,
790    transform_options: TransformOptions,
791    policy: Option<&crate::policy::SigningPolicy>,
792    provider: &dyn crate::provider::CryptoProvider,
793    execution_budget: &TransformExecutionBudget,
794) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
795    let doc = parse_signing_document(xml, policy)?;
796    let signature = find_signing_signature_node(&doc)?;
797    let signed_info = find_required_child(signature, "SignedInfo")?;
798    let references = parse_signing_references(signed_info)?;
799    if let Some(policy) = policy {
800        if references.len() > policy.resources.max_references {
801            return Err(crate::policy::PolicyViolation::ResourceLimit {
802                resource: "signature references",
803                maximum: policy.resources.max_references,
804                actual: references.len(),
805            }
806            .into());
807        }
808        for reference in &references {
809            if reference.transforms.len() > policy.resources.max_transforms_per_reference {
810                return Err(crate::policy::PolicyViolation::ResourceLimit {
811                    resource: "reference transforms",
812                    maximum: policy.resources.max_transforms_per_reference,
813                    actual: reference.transforms.len(),
814                }
815                .into());
816            }
817            if let Some(allowed) = policy.transforms.as_ref() {
818                for transform in &reference.transforms {
819                    let uri = transform.algorithm_uri();
820                    if !allowed.contains(uri) {
821                        return Err(crate::policy::PolicyViolation::Algorithm {
822                            operation: "signing transform",
823                            algorithm: uri.to_owned(),
824                        }
825                        .into());
826                    }
827                }
828                let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
829                if !transform_chain_produces_binary(initial_binary, &reference.transforms)
830                    && !allowed.contains(DEFAULT_IMPLICIT_C14N_URI)
831                {
832                    return Err(crate::policy::PolicyViolation::Algorithm {
833                        operation: "signing transform",
834                        algorithm: DEFAULT_IMPLICIT_C14N_URI.to_owned(),
835                    }
836                    .into());
837                }
838            }
839        }
840    }
841    let resolver = UriReferenceResolver::new(&doc);
842    references
843        .into_iter()
844        .enumerate()
845        .map(|(index, reference)| {
846            if policy.is_some_and(|policy| {
847                policy
848                    .digest_algorithms
849                    .as_ref()
850                    .is_some_and(|allowed| !allowed.contains(&reference.digest_method))
851            }) {
852                return Err(crate::policy::PolicyViolation::Algorithm {
853                    operation: "signing",
854                    algorithm: reference.digest_method.uri().to_string(),
855                }
856                .into());
857            }
858            let initial_data = resolver.dereference_with_budget(
859                &reference.uri,
860                execution_budget.node_set_materialization(),
861            )?;
862            let pre_digest = execute_transforms_with_options_and_budget(
863                signature,
864                initial_data,
865                &reference.transforms,
866                transform_options,
867                execution_budget,
868            )?;
869            let digest = super::compute_digest_with_provider(
870                provider,
871                reference.digest_method,
872                &pre_digest,
873            )?;
874            let digest_value = base64::engine::general_purpose::STANDARD.encode(digest);
875            Ok(ComputedReferenceDigest {
876                index,
877                uri: reference.uri,
878                digest_method: reference.digest_method,
879                digest_value,
880            })
881        })
882        .collect()
883}
884
885/// Compute and fill all signing-template `<DigestValue>` elements.
886///
887/// This is the signing counterpart to verification reference processing: it
888/// dereferences each `<Reference>`, applies transforms, computes the digest,
889/// and writes the base64 digest into the matching `<DigestValue>` in document
890/// order.
891pub fn fill_reference_digest_values(xml: &str) -> Result<String, SigningDigestError> {
892    let execution_budget = TransformExecutionBudget::default();
893    fill_reference_digest_values_with_options(
894        xml,
895        TransformOptions::default(),
896        None,
897        crate::provider::default_provider(),
898        &execution_budget,
899    )
900}
901
902fn fill_reference_digest_values_with_options(
903    xml: &str,
904    transform_options: TransformOptions,
905    policy: Option<&crate::policy::SigningPolicy>,
906    provider: &dyn crate::provider::CryptoProvider,
907    execution_budget: &TransformExecutionBudget,
908) -> Result<String, SigningDigestError> {
909    let digest_values = compute_reference_digest_values_with_options(
910        xml,
911        transform_options,
912        policy,
913        provider,
914        execution_budget,
915    )?
916    .into_iter()
917    .map(|digest| digest.digest_value);
918    Ok(if let Some(policy) = policy {
919        fill_signed_info_digest_values_with_options(xml, digest_values, Some(policy))?
920    } else {
921        fill_signed_info_digest_values(xml, digest_values)?
922    })
923}
924
925fn canonicalize_signed_info(
926    xml: &str,
927    policy: &crate::policy::SigningPolicy,
928    execution_budget: &TransformExecutionBudget,
929) -> Result<(SignatureAlgorithm, Vec<u8>), SigningError> {
930    let doc = parse_signing_document(xml, Some(policy)).map_err(SigningDigestError::XmlParse)?;
931    let signature = find_signing_signature_node(&doc).map_err(SigningError::Digest)?;
932    let signed_info_node =
933        find_required_child(signature, "SignedInfo").map_err(SigningError::Digest)?;
934    let signed_info = parse_signed_info(signed_info_node)?;
935    if policy
936        .transforms
937        .as_ref()
938        .is_some_and(|allowed| !allowed.contains(signed_info.c14n_method.uri()))
939    {
940        return Err(crate::policy::PolicyViolation::Algorithm {
941            operation: "SignedInfo canonicalization",
942            algorithm: signed_info.c14n_method.uri().to_owned(),
943        }
944        .into());
945    }
946    let signed_info_subtree: HashSet<_> = signed_info_node
947        .descendants()
948        .map(|node: Node<'_, '_>| node.id())
949        .collect();
950    let mut canonical_signed_info = Vec::new();
951    canonicalize_bounded_with_xml_base_budget(
952        &doc,
953        Some(&|node| signed_info_subtree.contains(&node.id())),
954        &signed_info.c14n_method,
955        execution_budget.remaining_c14n_output(),
956        execution_budget.xml_base_resolution(),
957        &mut canonical_signed_info,
958    )
959    .map_err(|error| {
960        if is_output_limit_error(&error) {
961            SigningError::Digest(SigningDigestError::Transform(
962                TransformError::C14nOutputTooLarge {
963                    max_bytes: execution_budget.c14n_output_limit(),
964                },
965            ))
966        } else {
967            SigningError::Canonicalization(error)
968        }
969    })?;
970    Ok((signed_info.signature_method, canonical_signed_info))
971}
972
973fn parse_signing_document<'a>(
974    xml: &'a str,
975    policy: Option<&crate::policy::SigningPolicy>,
976) -> Result<Document<'a>, roxmltree::Error> {
977    super::mutation::parse_with_options(xml, policy)
978}
979
980fn parse_private_key_pem(private_key_pem: &str) -> Result<Vec<u8>, SigningKeyError> {
981    let (rest, pem) = x509_parser::pem::parse_x509_pem(private_key_pem.as_bytes())
982        .map_err(|_| SigningKeyError::InvalidKeyPem)?;
983    if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
984        return Err(SigningKeyError::InvalidKeyPem);
985    }
986    if pem.label != "PRIVATE KEY" {
987        return Err(SigningKeyError::InvalidKeyFormat { label: pem.label });
988    }
989    Ok(pem.contents)
990}
991
992fn find_signing_signature_node<'a>(
993    doc: &'a Document<'a>,
994) -> Result<Node<'a, 'a>, SigningDigestError> {
995    doc.descendants()
996        .rfind(|node| {
997            node.is_element()
998                && node.tag_name().name() == "Signature"
999                && node.tag_name().namespace() == Some(XMLDSIG_NS)
1000        })
1001        .ok_or(SigningDigestError::MissingElement {
1002            element: "Signature",
1003        })
1004}
1005
1006fn parse_signing_references(
1007    signed_info: Node<'_, '_>,
1008) -> Result<Vec<SigningReference>, SigningDigestError> {
1009    verify_ds_element(signed_info, "SignedInfo")?;
1010    let mut children = element_children(signed_info);
1011
1012    let c14n_node = children.next().ok_or(SigningDigestError::MissingElement {
1013        element: "CanonicalizationMethod",
1014    })?;
1015    verify_ds_element(c14n_node, "CanonicalizationMethod")?;
1016    required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
1017
1018    let signature_method_node = children.next().ok_or(SigningDigestError::MissingElement {
1019        element: "SignatureMethod",
1020    })?;
1021    verify_ds_element(signature_method_node, "SignatureMethod")?;
1022    required_algorithm_attr(signature_method_node, "SignatureMethod")?;
1023
1024    let mut references = Vec::new();
1025    let mut xpath_budget = XPathSignatureParseBudget::default();
1026    for child in children {
1027        verify_ds_element(child, "Reference")?;
1028        if references.len() == MAX_REFERENCES_PER_SIGNATURE {
1029            return Err(SigningDigestError::InvalidStructure(format!(
1030                "SignedInfo contains more than {MAX_REFERENCES_PER_SIGNATURE} Reference elements"
1031            )));
1032        }
1033        references.push(parse_signing_reference(child, &mut xpath_budget)?);
1034    }
1035    if references.is_empty() {
1036        return Err(SigningDigestError::MissingElement {
1037            element: "Reference",
1038        });
1039    }
1040    Ok(references)
1041}
1042
1043fn parse_signing_reference(
1044    reference_node: Node<'_, '_>,
1045    xpath_budget: &mut XPathSignatureParseBudget,
1046) -> Result<SigningReference, SigningDigestError> {
1047    let uri = reference_node
1048        .attribute("URI")
1049        .ok_or_else(|| {
1050            SigningDigestError::InvalidStructure(
1051                "signing Reference must include URI attribute".into(),
1052            )
1053        })?
1054        .to_string();
1055    let mut children = element_children(reference_node);
1056
1057    let mut transforms = Vec::new();
1058    let mut next = children.next().ok_or(SigningDigestError::MissingElement {
1059        element: "DigestMethod",
1060    })?;
1061    if next.tag_name().name() == "Transforms" && next.tag_name().namespace() == Some(XMLDSIG_NS) {
1062        transforms = parse_transforms_with_budget(next, xpath_budget)?;
1063        next = children.next().ok_or(SigningDigestError::MissingElement {
1064            element: "DigestMethod",
1065        })?;
1066    }
1067
1068    verify_ds_element(next, "DigestMethod")?;
1069    let digest_uri = required_algorithm_attr(next, "DigestMethod")?;
1070    let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
1071        SigningDigestError::UnsupportedAlgorithm {
1072            uri: digest_uri.to_string(),
1073        }
1074    })?;
1075    if !digest_method.signing_allowed() {
1076        return Err(SigningDigestError::SigningAlgorithmDisabled {
1077            uri: digest_method.uri(),
1078        });
1079    }
1080
1081    let digest_value_node = children.next().ok_or(SigningDigestError::MissingElement {
1082        element: "DigestValue",
1083    })?;
1084    verify_ds_element(digest_value_node, "DigestValue")?;
1085
1086    if let Some(unexpected) = children.next() {
1087        return Err(SigningDigestError::InvalidStructure(format!(
1088            "unexpected element <{}> after <DigestValue> in <Reference>",
1089            unexpected.tag_name().name()
1090        )));
1091    }
1092
1093    Ok(SigningReference {
1094        uri,
1095        transforms,
1096        digest_method,
1097    })
1098}
1099
1100fn find_required_child<'a>(
1101    parent: Node<'a, 'a>,
1102    child_name: &'static str,
1103) -> Result<Node<'a, 'a>, SigningDigestError> {
1104    parent
1105        .children()
1106        .find(|node| {
1107            node.is_element()
1108                && node.tag_name().name() == child_name
1109                && node.tag_name().namespace() == Some(XMLDSIG_NS)
1110        })
1111        .ok_or(SigningDigestError::MissingElement {
1112            element: child_name,
1113        })
1114}
1115
1116fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
1117    node.children().filter(Node::is_element)
1118}
1119
1120fn verify_ds_element(
1121    node: Node<'_, '_>,
1122    expected_name: &'static str,
1123) -> Result<(), SigningDigestError> {
1124    if !node.is_element() {
1125        return Err(SigningDigestError::InvalidStructure(format!(
1126            "expected element <{expected_name}>, got non-element node"
1127        )));
1128    }
1129    let tag = node.tag_name();
1130    if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
1131        return Err(SigningDigestError::InvalidStructure(format!(
1132            "expected <ds:{expected_name}>, got <{}>",
1133            tag.name()
1134        )));
1135    }
1136    Ok(())
1137}
1138
1139fn required_algorithm_attr<'a>(
1140    node: Node<'a, 'a>,
1141    element_name: &'static str,
1142) -> Result<&'a str, SigningDigestError> {
1143    node.attribute("Algorithm").ok_or_else(|| {
1144        SigningDigestError::InvalidStructure(format!(
1145            "missing Algorithm attribute on <{element_name}>"
1146        ))
1147    })
1148}