1use crate::xml::dom::{Document, Node, NodeId};
9use base64::Engine;
10use hmac::{KeyInit, Mac};
11use p256::ecdsa::{
12 Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey,
13};
14use p256::pkcs8::{DecodePrivateKey, EncodePublicKey};
15use p384::ecdsa::{
16 Signature as P384Signature, SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey,
17};
18use p521::ecdsa::{
19 Signature as P521Signature, SigningKey as P521SigningKey, VerifyingKey as P521VerifyingKey,
20};
21use rsa::RsaPrivateKey;
22use rsa::pkcs1v15::Signature as RsaPkcs1v15Signature;
23use rsa::pkcs1v15::SigningKey as RsaPkcs1v15SigningKey;
24use rsa::signature::{RandomizedSigner, SignatureEncoding};
25use rsa::traits::PublicKeyParts;
26use sha1::Sha1;
27use sha2::{Sha224, Sha256, Sha384, Sha512};
28use signature::hazmat::{PrehashSigner, RandomizedPrehashSigner};
29use std::{
30 borrow::Cow,
31 collections::{HashMap, HashSet},
32 ops::Range,
33};
34use x509_parser::prelude::FromDer;
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 execute_transforms_with_options_and_budget, map_c14n_resource_policy_violation,
59 parse_transforms_with_budget, 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#[derive(Debug, Clone, PartialEq, Eq)]
72#[must_use = "use the computed digest value to fill the corresponding <DigestValue>"]
73pub struct ComputedReferenceDigest {
74 pub index: usize,
76 pub uri: String,
78 pub digest_method: DigestAlgorithm,
80 pub digest_value: String,
82}
83
84#[derive(Debug, thiserror::Error)]
86pub enum SigningDigestError {
87 #[error("cryptographic provider error: {0}")]
89 Provider(#[from] crate::provider::ProviderError),
90
91 #[error("signing policy violation: {0}")]
96 Policy(#[from] crate::policy::PolicyViolation),
97
98 #[error("XML parse error: {0}")]
100 XmlParse(#[from] crate::xml::dom::ParseError),
101
102 #[error("XML document error: {0}")]
104 Document(#[from] XmlDocumentError),
105
106 #[error("missing required element: <{element}>")]
108 MissingElement {
109 element: &'static str,
111 },
112
113 #[error("invalid signing template: {0}")]
115 InvalidStructure(String),
116
117 #[error("unsupported digest algorithm: {uri}")]
119 UnsupportedAlgorithm {
120 uri: String,
122 },
123
124 #[error("digest algorithm is disabled for signing: {uri}")]
126 SigningAlgorithmDisabled {
127 uri: &'static str,
129 },
130
131 #[error("reference processing error: {0}")]
133 Transform(#[from] TransformError),
134
135 #[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#[derive(Debug, thiserror::Error)]
148pub enum SigningError {
149 #[error("signing policy violation: {0}")]
151 Policy(#[from] crate::policy::PolicyViolation),
152
153 #[error("signing digest pass failed: {0}")]
155 Digest(SigningDigestError),
156
157 #[error("failed to parse SignedInfo after digest fill: {0}")]
159 ParseSignedInfo(super::parse::ParseError),
160
161 #[error("SignedInfo canonicalization failed: {0}")]
163 Canonicalization(#[from] crate::c14n::C14nError),
164
165 #[error("signing key error: {0}")]
167 Key(#[from] SigningKeyError),
168
169 #[error("signature output must be {expected} bytes, got {actual}")]
171 InvalidSignatureOutputLength {
172 expected: usize,
174 actual: usize,
176 },
177
178 #[error("XML mutation error: {0}")]
180 XmlMutation(XmlMutationError),
181
182 #[error("KeyInfo writer error: {0}")]
184 KeyInfo(#[from] KeyInfoWriteError),
185
186 #[error("XML document error: {0}")]
188 Document(#[from] XmlDocumentError),
189
190 #[error("signature template error: {0}")]
192 Template(SignatureBuilderError),
193}
194
195impl From<OperationPlanError> for SigningError {
196 fn from(error: OperationPlanError) -> Self {
197 SigningDigestError::from(error).into()
198 }
199}
200
201impl From<SigningDigestError> for SigningError {
202 fn from(error: SigningDigestError) -> Self {
203 match error {
204 SigningDigestError::XmlMutation(XmlMutationError::Policy(error)) => Self::Policy(error),
205 SigningDigestError::Policy(error)
206 | SigningDigestError::Transform(TransformError::Policy(error)) => Self::Policy(error),
207 SigningDigestError::Document(error) => Self::Document(error),
208 error => Self::Digest(error),
209 }
210 }
211}
212
213impl From<super::parse::ParseError> for SigningError {
214 fn from(error: super::parse::ParseError) -> Self {
215 match error {
216 super::parse::ParseError::Policy(error)
217 | super::parse::ParseError::Transform(TransformError::Policy(error)) => {
218 Self::Policy(error)
219 }
220 error => Self::ParseSignedInfo(error),
221 }
222 }
223}
224
225impl From<XmlMutationError> for SigningError {
226 fn from(error: XmlMutationError) -> Self {
227 match error {
228 XmlMutationError::Policy(error) => Self::Policy(error),
229 error => Self::XmlMutation(error),
230 }
231 }
232}
233
234impl From<SignatureBuilderError> for SigningError {
235 fn from(error: SignatureBuilderError) -> Self {
236 match error {
237 SignatureBuilderError::Policy(error) => Self::Policy(error),
238 error => Self::Template(error),
239 }
240 }
241}
242
243#[derive(Debug, thiserror::Error)]
245#[non_exhaustive]
246pub enum SigningKeyError {
247 #[error("cryptographic provider error: {0}")]
249 Provider(#[from] crate::provider::ProviderError),
250
251 #[error("invalid PEM private key")]
253 InvalidKeyPem,
254
255 #[error("invalid key format: expected PRIVATE KEY PEM, got {label}")]
257 InvalidKeyFormat {
258 label: String,
260 },
261
262 #[error("invalid PKCS#8 private key DER")]
264 InvalidKeyDer,
265
266 #[error("signing key does not support algorithm: {uri}")]
268 UnsupportedAlgorithm {
269 uri: String,
271 },
272
273 #[error("private-key signing operation failed")]
275 SigningFailed,
276
277 #[error("failed to encode signing public key as SPKI DER")]
279 PublicKeyEncodingFailed,
280
281 #[error("invalid signing public-key metadata")]
283 InvalidPublicKeyInfo,
284}
285
286#[derive(Debug, Clone, PartialEq, Eq)]
288#[non_exhaustive]
289pub enum SigningPublicKeyInfo {
290 Rsa {
292 spki_der: Vec<u8>,
294 modulus: Vec<u8>,
296 exponent: Vec<u8>,
298 },
299 Ec {
301 spki_der: Vec<u8>,
303 curve_oid: &'static str,
305 public_key: Vec<u8>,
307 },
308 Dsa {
310 spki_der: Vec<u8>,
312 p: Vec<u8>,
314 q: Vec<u8>,
316 g: Vec<u8>,
318 y: Vec<u8>,
320 modulus_bits: usize,
322 component_len: usize,
324 },
325 Hmac {
327 key_bits: usize,
329 },
330}
331
332impl SigningPublicKeyInfo {
333 #[must_use]
335 pub fn spki_der(&self) -> Option<&[u8]> {
336 match self {
337 Self::Rsa { spki_der, .. } | Self::Ec { spki_der, .. } | Self::Dsa { spki_der, .. } => {
338 Some(spki_der)
339 }
340 Self::Hmac { .. } => None,
341 }
342 }
343}
344
345pub fn validate_signing_key(
350 key: &dyn SigningKey,
351 algorithm: SignatureAlgorithm,
352 policy: &crate::policy::SigningPolicy,
353) -> Result<(), SigningError> {
354 policy.resources.validate_key_candidates(1)?;
355 policy.check_signature_algorithm(algorithm)?;
356 expected_signature_output_len(key, algorithm, policy, None).map(|_| ())
357}
358
359fn expected_signature_output_len(
360 key: &dyn SigningKey,
361 algorithm: SignatureAlgorithm,
362 policy: &crate::policy::SigningPolicy,
363 hmac_output_length_bits: Option<usize>,
364) -> Result<usize, SigningError> {
365 let public_key = key.public_key_info()?;
366 let expected = match (algorithm, public_key) {
367 (
368 SignatureAlgorithm::RsaSha1
369 | SignatureAlgorithm::RsaSha224
370 | SignatureAlgorithm::RsaSha256
371 | SignatureAlgorithm::RsaSha384
372 | SignatureAlgorithm::RsaSha512,
373 SigningPublicKeyInfo::Rsa {
374 modulus, exponent, ..
375 },
376 ) => policy
377 .rsa_keys
378 .validate_components("signing", &modulus, &exponent)?,
379 (
380 SignatureAlgorithm::EcdsaSha1
381 | SignatureAlgorithm::EcdsaSha224
382 | SignatureAlgorithm::EcdsaSha256
383 | SignatureAlgorithm::EcdsaSha384
384 | SignatureAlgorithm::EcdsaSha512,
385 SigningPublicKeyInfo::Ec { public_key, .. },
386 ) if public_key.first() == Some(&0x04)
387 && public_key.len() > 1
388 && (public_key.len() - 1).is_multiple_of(2) =>
389 {
390 public_key.len() - 1
393 }
394 (
395 algorithm @ (SignatureAlgorithm::DsaSha1 | SignatureAlgorithm::DsaSha256),
396 SigningPublicKeyInfo::Dsa {
397 modulus_bits,
398 component_len,
399 ..
400 },
401 ) => {
402 policy.dsa_keys.validate_modulus_bits(modulus_bits)?;
403 let required_component_len = algorithm
404 .dsa_component_len()
405 .expect("DSA algorithm matched above");
406 if component_len != required_component_len {
407 return Err(crate::policy::PolicyViolation::InvalidKeyMaterial {
408 operation: "signing",
409 key_type: "DSA",
410 reason: match algorithm {
411 SignatureAlgorithm::DsaSha1 => "DSA-SHA1 requires a 160-bit q parameter",
412 SignatureAlgorithm::DsaSha256 => {
413 "DSA-SHA256 requires a 256-bit q parameter"
414 }
415 _ => unreachable!("DSA algorithm matched above"),
416 },
417 }
418 .into());
419 }
420 component_len.saturating_mul(2)
421 }
422 (
423 SignatureAlgorithm::HmacSha1
424 | SignatureAlgorithm::HmacSha224
425 | SignatureAlgorithm::HmacSha256
426 | SignatureAlgorithm::HmacSha384
427 | SignatureAlgorithm::HmacSha512,
428 SigningPublicKeyInfo::Hmac { key_bits },
429 ) => {
430 policy.hmac.validate_key_bits(key_bits)?;
431 let output_bits = hmac_output_length_bits.unwrap_or(
432 algorithm
433 .hmac_output_bits()
434 .ok_or(SigningKeyError::InvalidPublicKeyInfo)?,
435 );
436 policy.hmac.validate_output(algorithm, output_bits)?;
437 output_bits / 8
438 }
439 (
440 SignatureAlgorithm::RsaSha1
441 | SignatureAlgorithm::RsaSha224
442 | SignatureAlgorithm::RsaSha256
443 | SignatureAlgorithm::RsaSha384
444 | SignatureAlgorithm::RsaSha512,
445 SigningPublicKeyInfo::Ec { .. }
446 | SigningPublicKeyInfo::Dsa { .. }
447 | SigningPublicKeyInfo::Hmac { .. },
448 )
449 | (
450 SignatureAlgorithm::EcdsaSha1
451 | SignatureAlgorithm::EcdsaSha224
452 | SignatureAlgorithm::EcdsaSha256
453 | SignatureAlgorithm::EcdsaSha384
454 | SignatureAlgorithm::EcdsaSha512,
455 SigningPublicKeyInfo::Rsa { .. }
456 | SigningPublicKeyInfo::Dsa { .. }
457 | SigningPublicKeyInfo::Hmac { .. },
458 ) => {
459 return Err(SigningKeyError::UnsupportedAlgorithm {
460 uri: algorithm.uri().to_owned(),
461 }
462 .into());
463 }
464 _ => return Err(SigningKeyError::InvalidPublicKeyInfo.into()),
465 };
466 Ok(expected)
467}
468
469fn validate_signature_output(expected: usize, signature: &[u8]) -> Result<(), SigningError> {
470 if signature.len() != expected {
471 return Err(SigningError::InvalidSignatureOutputLength {
472 expected,
473 actual: signature.len(),
474 });
475 }
476 Ok(())
477}
478
479pub trait SigningKey {
481 fn sign(
483 &self,
484 algorithm: SignatureAlgorithm,
485 canonical_signed_info: &[u8],
486 ) -> Result<Vec<u8>, SigningKeyError>;
487
488 fn sign_with_provider(
493 &self,
494 provider: &dyn crate::provider::CryptoProvider,
495 algorithm: SignatureAlgorithm,
496 canonical_signed_info: &[u8],
497 ) -> Result<Vec<u8>, SigningKeyError> {
498 let _ = provider;
499 self.sign(algorithm, canonical_signed_info)
500 }
501
502 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError>;
504}
505
506pub trait KeyInfoWriter {
508 fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError>;
510
511 fn write_key_info_with_provider(
516 &self,
517 signing_key: &dyn SigningKey,
518 provider: &dyn crate::provider::CryptoProvider,
519 ) -> Result<String, KeyInfoWriteError> {
520 let _ = provider;
521 self.write_key_info(signing_key)
522 }
523}
524
525#[derive(Debug, thiserror::Error)]
527#[non_exhaustive]
528pub enum KeyInfoWriteError {
529 #[error("cryptographic provider error: {0}")]
531 Provider(#[from] crate::provider::ProviderError),
532
533 #[error("invalid PEM certificate")]
535 InvalidCertificatePem,
536
537 #[error("invalid certificate format: expected CERTIFICATE PEM, got {label}")]
539 InvalidCertificateFormat {
540 label: String,
542 },
543
544 #[error("invalid X.509 certificate DER")]
546 InvalidCertificateDer,
547
548 #[error("X.509 certificate chain must not be empty")]
550 EmptyCertificateChain,
551
552 #[error("signing key public-key extraction failed: {0}")]
554 SigningKey(#[from] SigningKeyError),
555
556 #[error("signing key has no DER-encodable public key")]
558 MissingPublicKey,
559
560 #[error("signing key cannot be represented as XMLDSig KeyValue")]
562 UnsupportedKeyValue,
563
564 #[error("X.509 certificate public key does not match signing key")]
566 CertificateKeyMismatch,
567}
568
569#[derive(Debug, Clone, Copy, Default)]
571pub struct DerEncodedKeyValueInfoWriter;
572
573impl KeyInfoWriter for DerEncodedKeyValueInfoWriter {
574 fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
575 let public_key = signing_key.public_key_info()?;
576 let spki_der = public_key
577 .spki_der()
578 .ok_or(KeyInfoWriteError::MissingPublicKey)?;
579 let encoded = base64::engine::general_purpose::STANDARD.encode(spki_der);
580 Ok(format!(
581 "<dsig11:DEREncodedKeyValue xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\">{encoded}</dsig11:DEREncodedKeyValue>"
582 ))
583 }
584}
585
586#[derive(Debug, Clone, Copy, Default)]
588pub struct KeyValueInfoWriter;
589
590impl KeyInfoWriter for KeyValueInfoWriter {
591 fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
592 let encode = |bytes: &[u8]| base64::engine::general_purpose::STANDARD.encode(bytes);
593 match signing_key.public_key_info()? {
594 SigningPublicKeyInfo::Rsa {
595 modulus, exponent, ..
596 } => Ok(format!(
597 "<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>",
598 encode(&modulus),
599 encode(&exponent)
600 )),
601 SigningPublicKeyInfo::Ec {
602 curve_oid,
603 public_key,
604 ..
605 } => Ok(format!(
606 "<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>",
607 encode(&public_key)
608 )),
609 SigningPublicKeyInfo::Dsa { p, q, g, y, .. } => Ok(format!(
610 "<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>",
611 encode(&p),
612 encode(&q),
613 encode(&g),
614 encode(&y)
615 )),
616 SigningPublicKeyInfo::Hmac { .. } => Err(KeyInfoWriteError::UnsupportedKeyValue),
617 }
618 }
619}
620
621pub struct X509CertificateKeyInfoWriter {
623 certificates_der: Vec<Vec<u8>>,
624}
625
626impl X509CertificateKeyInfoWriter {
627 pub fn from_pem(certificate_pem: &str) -> Result<Self, KeyInfoWriteError> {
629 Self::from_pem_chain([certificate_pem])
630 }
631
632 pub fn from_pem_chain<I, S>(certificate_pems: I) -> Result<Self, KeyInfoWriteError>
638 where
639 I: IntoIterator<Item = S>,
640 S: AsRef<str>,
641 {
642 let mut certificates_der = Vec::new();
643 for certificate_pem in certificate_pems {
644 certificates_der.push(parse_certificate_pem(certificate_pem.as_ref())?);
645 }
646 Self::from_der_chain(certificates_der)
647 }
648
649 pub fn from_der(certificate_der: &[u8]) -> Result<Self, KeyInfoWriteError> {
651 Self::from_der_chain([certificate_der])
652 }
653
654 pub fn from_der_chain<I, B>(certificates_der: I) -> Result<Self, KeyInfoWriteError>
660 where
661 I: IntoIterator<Item = B>,
662 B: AsRef<[u8]>,
663 {
664 let certificates_der = certificates_der
665 .into_iter()
666 .map(|certificate_der| {
667 let certificate_der = certificate_der.as_ref();
668 let (rest, _) =
669 x509_parser::certificate::X509Certificate::from_der(certificate_der)
670 .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
671 if !rest.is_empty() {
672 return Err(KeyInfoWriteError::InvalidCertificateDer);
673 }
674 Ok(certificate_der.to_vec())
675 })
676 .collect::<Result<Vec<_>, _>>()?;
677 if certificates_der.is_empty() {
678 return Err(KeyInfoWriteError::EmptyCertificateChain);
679 }
680 Ok(Self { certificates_der })
681 }
682}
683
684fn parse_certificate_pem(certificate_pem: &str) -> Result<Vec<u8>, KeyInfoWriteError> {
685 let (rest, pem) = x509_parser::pem::parse_x509_pem(certificate_pem.as_bytes())
686 .map_err(|_| KeyInfoWriteError::InvalidCertificatePem)?;
687 if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
688 return Err(KeyInfoWriteError::InvalidCertificatePem);
689 }
690 if pem.label != "CERTIFICATE" {
691 return Err(KeyInfoWriteError::InvalidCertificateFormat { label: pem.label });
692 }
693 Ok(pem.contents)
694}
695
696impl KeyInfoWriter for X509CertificateKeyInfoWriter {
697 fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
698 let leaf_der = &self.certificates_der[0];
699 let (rest, certificate) = x509_parser::certificate::X509Certificate::from_der(leaf_der)
700 .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
701 if !rest.is_empty() {
702 return Err(KeyInfoWriteError::InvalidCertificateDer);
703 }
704 let signing_public_key = signing_key.public_key_info()?;
705 if signing_public_key.spki_der() != Some(certificate.public_key().raw) {
706 return Err(KeyInfoWriteError::CertificateKeyMismatch);
707 }
708
709 let mut xml = format!("<X509Data xmlns=\"{XMLDSIG_NS}\">");
710 for certificate_der in &self.certificates_der {
711 let certificate_b64 = base64::engine::general_purpose::STANDARD.encode(certificate_der);
712 xml.push_str("<X509Certificate>");
713 xml.push_str(&certificate_b64);
714 xml.push_str("</X509Certificate>");
715 }
716 xml.push_str("</X509Data>");
717 Ok(xml)
718 }
719}
720
721pub struct X509DigestKeyInfoWriter {
723 certificate_der: Vec<u8>,
724 certificate_spki_der: Vec<u8>,
725 digest_algorithm: DigestAlgorithm,
726}
727
728impl X509DigestKeyInfoWriter {
729 pub fn from_der(
731 certificate_der: &[u8],
732 digest_algorithm: DigestAlgorithm,
733 ) -> Result<Self, KeyInfoWriteError> {
734 let (rest, certificate) =
735 x509_parser::certificate::X509Certificate::from_der(certificate_der)
736 .map_err(|_| KeyInfoWriteError::InvalidCertificateDer)?;
737 if !rest.is_empty() {
738 return Err(KeyInfoWriteError::InvalidCertificateDer);
739 }
740 Ok(Self {
741 certificate_der: certificate_der.to_vec(),
742 certificate_spki_der: certificate.public_key().raw.to_vec(),
743 digest_algorithm,
744 })
745 }
746
747 pub fn from_pem(
749 certificate_pem: &str,
750 digest_algorithm: DigestAlgorithm,
751 ) -> Result<Self, KeyInfoWriteError> {
752 Self::from_der(&parse_certificate_pem(certificate_pem)?, digest_algorithm)
753 }
754}
755
756impl KeyInfoWriter for X509DigestKeyInfoWriter {
757 fn write_key_info(&self, signing_key: &dyn SigningKey) -> Result<String, KeyInfoWriteError> {
758 self.write_key_info_with_provider(signing_key, crate::provider::default_provider())
759 }
760
761 fn write_key_info_with_provider(
762 &self,
763 signing_key: &dyn SigningKey,
764 provider: &dyn crate::provider::CryptoProvider,
765 ) -> Result<String, KeyInfoWriteError> {
766 if signing_key.public_key_info()?.spki_der() != Some(self.certificate_spki_der.as_slice()) {
767 return Err(KeyInfoWriteError::CertificateKeyMismatch);
768 }
769 let digest = super::compute_digest_with_provider(
770 provider,
771 self.digest_algorithm,
772 &self.certificate_der,
773 )?;
774 let encoded = base64::engine::general_purpose::STANDARD.encode(digest);
775 Ok(format!(
776 "<ds:X509Data xmlns:ds=\"{XMLDSIG_NS}\"><dsig11:X509Digest xmlns:dsig11=\"http://www.w3.org/2009/xmldsig11#\" Algorithm=\"{}\">{encoded}</dsig11:X509Digest></ds:X509Data>",
777 self.digest_algorithm.uri()
778 ))
779 }
780}
781
782pub struct RsaSigningKey {
784 key: RsaPrivateKey,
785}
786
787impl RsaSigningKey {
788 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
790 let private_key_der = parse_private_key_pem(private_key_pem)?;
791 Self::from_pkcs8_der(&private_key_der)
792 }
793
794 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
796 let key = RsaPrivateKey::from_pkcs8_der(private_key_der)
797 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
798 Ok(Self { key })
799 }
800
801 pub fn from_pkcs8_encrypted_pem(
803 private_key_pem: &str,
804 password: impl AsRef<[u8]>,
805 ) -> Result<Self, SigningKeyError> {
806 let key = RsaPrivateKey::from_pkcs8_encrypted_pem(private_key_pem, password)
807 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
808 Ok(Self { key })
809 }
810
811 pub fn from_pkcs8_encrypted_der(
813 private_key_der: &[u8],
814 password: impl AsRef<[u8]>,
815 ) -> Result<Self, SigningKeyError> {
816 let key = RsaPrivateKey::from_pkcs8_encrypted_der(private_key_der, password)
817 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
818 Ok(Self { key })
819 }
820}
821
822impl SigningKey for RsaSigningKey {
823 fn sign(
824 &self,
825 algorithm: SignatureAlgorithm,
826 canonical_signed_info: &[u8],
827 ) -> Result<Vec<u8>, SigningKeyError> {
828 self.sign_with_provider(
829 crate::provider::default_provider(),
830 algorithm,
831 canonical_signed_info,
832 )
833 }
834
835 fn sign_with_provider(
836 &self,
837 provider: &dyn crate::provider::CryptoProvider,
838 algorithm: SignatureAlgorithm,
839 canonical_signed_info: &[u8],
840 ) -> Result<Vec<u8>, SigningKeyError> {
841 match algorithm {
842 SignatureAlgorithm::RsaSha1 => sign_rsa_pkcs1v15_with_rng(
843 provider,
844 RsaPkcs1v15SigningKey::<Sha1>::new(self.key.clone()),
845 canonical_signed_info,
846 ),
847 SignatureAlgorithm::RsaSha224 => sign_rsa_pkcs1v15_with_rng(
848 provider,
849 RsaPkcs1v15SigningKey::<Sha224>::new(self.key.clone()),
850 canonical_signed_info,
851 ),
852 SignatureAlgorithm::RsaSha256 => sign_rsa_pkcs1v15_with_rng(
853 provider,
854 RsaPkcs1v15SigningKey::<Sha256>::new(self.key.clone()),
855 canonical_signed_info,
856 ),
857 SignatureAlgorithm::RsaSha384 => sign_rsa_pkcs1v15_with_rng(
858 provider,
859 RsaPkcs1v15SigningKey::<Sha384>::new(self.key.clone()),
860 canonical_signed_info,
861 ),
862 SignatureAlgorithm::RsaSha512 => sign_rsa_pkcs1v15_with_rng(
863 provider,
864 RsaPkcs1v15SigningKey::<Sha512>::new(self.key.clone()),
865 canonical_signed_info,
866 ),
867 _ => Err(SigningKeyError::UnsupportedAlgorithm {
868 uri: algorithm.uri().to_string(),
869 }),
870 }
871 }
872
873 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
874 let public_key = self.key.to_public_key();
875 let spki_der = public_key
876 .to_public_key_der()
877 .map(|doc| doc.as_bytes().to_vec())
878 .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
879 Ok(SigningPublicKeyInfo::Rsa {
880 spki_der,
881 modulus: public_key.n().to_be_bytes_trimmed_vartime().into_vec(),
882 exponent: public_key.e().to_be_bytes_trimmed_vartime().into_vec(),
883 })
884 }
885}
886
887pub struct HmacSigningKey {
891 secret: Zeroizing<Vec<u8>>,
892}
893
894impl HmacSigningKey {
895 pub fn new(secret: impl Into<Vec<u8>>) -> Result<Self, SigningKeyError> {
897 let secret = secret.into();
898 if secret.is_empty() {
899 return Err(SigningKeyError::InvalidKeyDer);
900 }
901 Ok(Self {
902 secret: Zeroizing::new(secret),
903 })
904 }
905}
906
907impl SigningKey for HmacSigningKey {
908 fn sign(
909 &self,
910 algorithm: SignatureAlgorithm,
911 canonical_signed_info: &[u8],
912 ) -> Result<Vec<u8>, SigningKeyError> {
913 macro_rules! sign_hmac {
914 ($digest:ty) => {{
915 let mut mac = hmac::Hmac::<$digest>::new_from_slice(&self.secret)
916 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
917 mac.update(canonical_signed_info);
918 mac.finalize().into_bytes().to_vec()
919 }};
920 }
921 Ok(match algorithm {
922 SignatureAlgorithm::HmacSha1 => sign_hmac!(sha1::Sha1),
923 SignatureAlgorithm::HmacSha224 => sign_hmac!(sha2::Sha224),
924 SignatureAlgorithm::HmacSha256 => sign_hmac!(sha2::Sha256),
925 SignatureAlgorithm::HmacSha384 => sign_hmac!(sha2::Sha384),
926 SignatureAlgorithm::HmacSha512 => sign_hmac!(sha2::Sha512),
927 _ => {
928 return Err(SigningKeyError::UnsupportedAlgorithm {
929 uri: algorithm.uri().to_owned(),
930 });
931 }
932 })
933 }
934
935 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
936 Ok(SigningPublicKeyInfo::Hmac {
937 key_bits: self.secret.len().saturating_mul(8),
938 })
939 }
940}
941
942pub struct DsaSigningKey {
944 key: dsa::SigningKey,
945}
946
947impl DsaSigningKey {
948 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
950 let private_key_der = parse_private_key_pem(private_key_pem)?;
951 Self::from_pkcs8_der(&private_key_der)
952 }
953
954 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
956 let key = dsa::SigningKey::from_pkcs8_der(private_key_der)
957 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
958 Ok(Self { key })
959 }
960
961 pub fn from_pkcs8_encrypted_pem(
963 private_key_pem: &str,
964 password: impl AsRef<[u8]>,
965 ) -> Result<Self, SigningKeyError> {
966 let key = dsa::SigningKey::from_pkcs8_encrypted_pem(private_key_pem, password)
967 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
968 Ok(Self { key })
969 }
970
971 pub fn from_pkcs8_encrypted_der(
973 private_key_der: &[u8],
974 password: impl AsRef<[u8]>,
975 ) -> Result<Self, SigningKeyError> {
976 let key = dsa::SigningKey::from_pkcs8_encrypted_der(private_key_der, password)
977 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
978 Ok(Self { key })
979 }
980}
981
982impl SigningKey for DsaSigningKey {
983 fn sign(
984 &self,
985 algorithm: SignatureAlgorithm,
986 canonical_signed_info: &[u8],
987 ) -> Result<Vec<u8>, SigningKeyError> {
988 self.sign_with_provider(
989 crate::provider::default_provider(),
990 algorithm,
991 canonical_signed_info,
992 )
993 }
994
995 fn sign_with_provider(
996 &self,
997 provider: &dyn crate::provider::CryptoProvider,
998 algorithm: SignatureAlgorithm,
999 canonical_signed_info: &[u8],
1000 ) -> Result<Vec<u8>, SigningKeyError> {
1001 let digest_algorithm = match algorithm {
1002 SignatureAlgorithm::DsaSha1 => DigestAlgorithm::Sha1,
1003 SignatureAlgorithm::DsaSha256 => DigestAlgorithm::Sha256,
1004 _ => {
1005 return Err(SigningKeyError::UnsupportedAlgorithm {
1006 uri: algorithm.uri().to_owned(),
1007 });
1008 }
1009 };
1010 let component_len =
1011 usize::try_from(self.key.verifying_key().components().q().bits_vartime())
1012 .map_err(|_| SigningKeyError::InvalidPublicKeyInfo)?
1013 .div_ceil(8);
1014 if algorithm.dsa_component_len() != Some(component_len) {
1015 return Err(SigningKeyError::InvalidPublicKeyInfo);
1016 }
1017 let digest =
1018 super::compute_digest_with_provider(provider, digest_algorithm, canonical_signed_info)?;
1019 let mut rng = crate::provider::ProviderRng(provider);
1020 let signature: dsa::Signature = self
1021 .key
1022 .sign_prehash_with_rng(&mut rng, &digest)
1023 .map_err(|_| SigningKeyError::SigningFailed)?;
1024 let mut output = Vec::with_capacity(component_len.saturating_mul(2));
1025 for component in [signature.r(), signature.s()] {
1026 let bytes = component.to_be_bytes_trimmed_vartime();
1027 if bytes.len() > component_len {
1028 return Err(SigningKeyError::SigningFailed);
1029 }
1030 output.resize(output.len() + component_len - bytes.len(), 0);
1031 output.extend_from_slice(bytes.as_ref());
1032 }
1033 Ok(output)
1034 }
1035
1036 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1037 let verifying_key = self.key.verifying_key();
1038 let spki_der = verifying_key
1039 .to_public_key_der()
1040 .map(|doc| doc.as_bytes().to_vec())
1041 .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)?;
1042 let modulus_bits = usize::try_from(verifying_key.components().p().bits_vartime())
1043 .map_err(|_| SigningKeyError::InvalidPublicKeyInfo)?;
1044 let component_len = usize::try_from(verifying_key.components().q().bits_vartime())
1045 .map_err(|_| SigningKeyError::InvalidPublicKeyInfo)?
1046 .div_ceil(8);
1047 let components = verifying_key.components();
1048 Ok(SigningPublicKeyInfo::Dsa {
1049 spki_der,
1050 p: components.p().to_be_bytes_trimmed_vartime().to_vec(),
1051 q: components.q().to_be_bytes_trimmed_vartime().to_vec(),
1052 g: components.g().to_be_bytes_trimmed_vartime().to_vec(),
1053 y: verifying_key.y().to_be_bytes_trimmed_vartime().to_vec(),
1054 modulus_bits,
1055 component_len,
1056 })
1057 }
1058}
1059
1060fn sign_rsa_pkcs1v15_with_rng(
1061 provider: &dyn crate::provider::CryptoProvider,
1062 key: impl RandomizedSigner<RsaPkcs1v15Signature>,
1063 canonical_signed_info: &[u8],
1064) -> Result<Vec<u8>, SigningKeyError> {
1065 let mut rng = crate::provider::ProviderRng(provider);
1066 let signature = key
1067 .try_sign_with_rng(&mut rng, canonical_signed_info)
1068 .map_err(|_| SigningKeyError::SigningFailed)?;
1069 Ok(signature.to_vec())
1070}
1071
1072fn ecdsa_digest_algorithm(
1073 algorithm: SignatureAlgorithm,
1074) -> Result<DigestAlgorithm, SigningKeyError> {
1075 match algorithm {
1076 SignatureAlgorithm::EcdsaSha1 => Ok(DigestAlgorithm::Sha1),
1077 SignatureAlgorithm::EcdsaSha224 => Ok(DigestAlgorithm::Sha224),
1078 SignatureAlgorithm::EcdsaSha256 => Ok(DigestAlgorithm::Sha256),
1079 SignatureAlgorithm::EcdsaSha384 => Ok(DigestAlgorithm::Sha384),
1080 SignatureAlgorithm::EcdsaSha512 => Ok(DigestAlgorithm::Sha512),
1081 _ => Err(SigningKeyError::UnsupportedAlgorithm {
1082 uri: algorithm.uri().to_owned(),
1083 }),
1084 }
1085}
1086
1087fn sign_ecdsa_with_provider<S, K>(
1088 key: &K,
1089 provider: &dyn crate::provider::CryptoProvider,
1090 algorithm: SignatureAlgorithm,
1091 canonical_signed_info: &[u8],
1092) -> Result<Vec<u8>, SigningKeyError>
1093where
1094 K: PrehashSigner<S>,
1095 S: SignatureEncoding,
1096{
1097 let digest_algorithm = ecdsa_digest_algorithm(algorithm)?;
1098 let prehash =
1099 super::compute_digest_with_provider(provider, digest_algorithm, canonical_signed_info)?;
1100 let signature = key
1101 .sign_prehash(&prehash)
1102 .map_err(|_| SigningKeyError::SigningFailed)?;
1103 Ok(signature.to_vec())
1104}
1105
1106trait EcdsaPublicKeyEncoding {
1107 fn spki_der(&self) -> Result<Vec<u8>, SigningKeyError>;
1108 fn uncompressed_sec1(&self) -> Vec<u8>;
1109}
1110
1111macro_rules! impl_ecdsa_public_key_encoding {
1112 ($key:ty) => {
1113 impl EcdsaPublicKeyEncoding for $key {
1114 fn spki_der(&self) -> Result<Vec<u8>, SigningKeyError> {
1115 self.to_public_key_der()
1116 .map(|document| document.as_bytes().to_vec())
1117 .map_err(|_| SigningKeyError::PublicKeyEncodingFailed)
1118 }
1119
1120 fn uncompressed_sec1(&self) -> Vec<u8> {
1121 self.to_sec1_point(false).as_bytes().to_vec()
1122 }
1123 }
1124 };
1125}
1126
1127impl_ecdsa_public_key_encoding!(P256VerifyingKey);
1128impl_ecdsa_public_key_encoding!(P384VerifyingKey);
1129impl_ecdsa_public_key_encoding!(P521VerifyingKey);
1130
1131fn ecdsa_public_key_info(
1132 key: &impl EcdsaPublicKeyEncoding,
1133 curve_oid: &'static str,
1134) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1135 Ok(SigningPublicKeyInfo::Ec {
1136 spki_der: key.spki_der()?,
1137 curve_oid,
1138 public_key: key.uncompressed_sec1(),
1139 })
1140}
1141
1142pub struct EcdsaP256SigningKey {
1144 key: P256SigningKey,
1145}
1146
1147impl EcdsaP256SigningKey {
1148 pub fn from_sec1_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1150 let key = p256::SecretKey::from_sec1_pem(private_key_pem)
1151 .map(P256SigningKey::from)
1152 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1153 Ok(Self { key })
1154 }
1155
1156 pub fn from_sec1_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1158 let key = p256::SecretKey::from_sec1_der(private_key_der)
1159 .map(P256SigningKey::from)
1160 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1161 Ok(Self { key })
1162 }
1163
1164 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1166 let private_key_der = parse_private_key_pem(private_key_pem)?;
1167 Self::from_pkcs8_der(&private_key_der)
1168 }
1169
1170 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1172 let key = P256SigningKey::from_pkcs8_der(private_key_der)
1173 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1174 Ok(Self { key })
1175 }
1176
1177 pub fn from_pkcs8_encrypted_pem(
1179 private_key_pem: &str,
1180 password: impl AsRef<[u8]>,
1181 ) -> Result<Self, SigningKeyError> {
1182 let key = P256SigningKey::from_pkcs8_encrypted_pem(private_key_pem, password)
1183 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1184 Ok(Self { key })
1185 }
1186
1187 pub fn from_pkcs8_encrypted_der(
1189 private_key_der: &[u8],
1190 password: impl AsRef<[u8]>,
1191 ) -> Result<Self, SigningKeyError> {
1192 let key = P256SigningKey::from_pkcs8_encrypted_der(private_key_der, password)
1193 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1194 Ok(Self { key })
1195 }
1196}
1197
1198impl SigningKey for EcdsaP256SigningKey {
1199 fn sign(
1200 &self,
1201 algorithm: SignatureAlgorithm,
1202 canonical_signed_info: &[u8],
1203 ) -> Result<Vec<u8>, SigningKeyError> {
1204 self.sign_with_provider(
1205 crate::provider::default_provider(),
1206 algorithm,
1207 canonical_signed_info,
1208 )
1209 }
1210
1211 fn sign_with_provider(
1212 &self,
1213 provider: &dyn crate::provider::CryptoProvider,
1214 algorithm: SignatureAlgorithm,
1215 canonical_signed_info: &[u8],
1216 ) -> Result<Vec<u8>, SigningKeyError> {
1217 sign_ecdsa_with_provider::<P256Signature, _>(
1218 &self.key,
1219 provider,
1220 algorithm,
1221 canonical_signed_info,
1222 )
1223 }
1224
1225 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1226 ecdsa_public_key_info(self.key.verifying_key(), EC_P256_OID)
1227 }
1228}
1229
1230pub struct EcdsaP384SigningKey {
1232 key: P384SigningKey,
1233}
1234
1235impl EcdsaP384SigningKey {
1236 pub fn from_sec1_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1238 let key = p384::SecretKey::from_sec1_pem(private_key_pem)
1239 .map(P384SigningKey::from)
1240 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1241 Ok(Self { key })
1242 }
1243
1244 pub fn from_sec1_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1246 let key = p384::SecretKey::from_sec1_der(private_key_der)
1247 .map(P384SigningKey::from)
1248 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1249 Ok(Self { key })
1250 }
1251
1252 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1254 let private_key_der = parse_private_key_pem(private_key_pem)?;
1255 Self::from_pkcs8_der(&private_key_der)
1256 }
1257
1258 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1260 let key = P384SigningKey::from_pkcs8_der(private_key_der)
1261 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1262 Ok(Self { key })
1263 }
1264
1265 pub fn from_pkcs8_encrypted_pem(
1267 private_key_pem: &str,
1268 password: impl AsRef<[u8]>,
1269 ) -> Result<Self, SigningKeyError> {
1270 let key = P384SigningKey::from_pkcs8_encrypted_pem(private_key_pem, password)
1271 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1272 Ok(Self { key })
1273 }
1274
1275 pub fn from_pkcs8_encrypted_der(
1277 private_key_der: &[u8],
1278 password: impl AsRef<[u8]>,
1279 ) -> Result<Self, SigningKeyError> {
1280 let key = P384SigningKey::from_pkcs8_encrypted_der(private_key_der, password)
1281 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1282 Ok(Self { key })
1283 }
1284}
1285
1286impl SigningKey for EcdsaP384SigningKey {
1287 fn sign(
1288 &self,
1289 algorithm: SignatureAlgorithm,
1290 canonical_signed_info: &[u8],
1291 ) -> Result<Vec<u8>, SigningKeyError> {
1292 self.sign_with_provider(
1293 crate::provider::default_provider(),
1294 algorithm,
1295 canonical_signed_info,
1296 )
1297 }
1298
1299 fn sign_with_provider(
1300 &self,
1301 provider: &dyn crate::provider::CryptoProvider,
1302 algorithm: SignatureAlgorithm,
1303 canonical_signed_info: &[u8],
1304 ) -> Result<Vec<u8>, SigningKeyError> {
1305 sign_ecdsa_with_provider::<P384Signature, _>(
1306 &self.key,
1307 provider,
1308 algorithm,
1309 canonical_signed_info,
1310 )
1311 }
1312
1313 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1314 ecdsa_public_key_info(self.key.verifying_key(), EC_P384_OID)
1315 }
1316}
1317
1318pub struct EcdsaP521SigningKey {
1320 key: P521SigningKey,
1321}
1322
1323impl EcdsaP521SigningKey {
1324 pub fn from_sec1_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1326 let key = p521::SecretKey::from_sec1_pem(private_key_pem)
1327 .map(P521SigningKey::from)
1328 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1329 Ok(Self { key })
1330 }
1331
1332 pub fn from_sec1_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1334 let key = p521::SecretKey::from_sec1_der(private_key_der)
1335 .map(P521SigningKey::from)
1336 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1337 Ok(Self { key })
1338 }
1339
1340 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
1342 let private_key_der = parse_private_key_pem(private_key_pem)?;
1343 Self::from_pkcs8_der(&private_key_der)
1344 }
1345
1346 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
1348 let key = P521SigningKey::from_pkcs8_der(private_key_der)
1349 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1350 Ok(Self { key })
1351 }
1352
1353 pub fn from_pkcs8_encrypted_pem(
1355 private_key_pem: &str,
1356 password: impl AsRef<[u8]>,
1357 ) -> Result<Self, SigningKeyError> {
1358 let key = P521SigningKey::from_pkcs8_encrypted_pem(private_key_pem, password)
1359 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1360 Ok(Self { key })
1361 }
1362
1363 pub fn from_pkcs8_encrypted_der(
1365 private_key_der: &[u8],
1366 password: impl AsRef<[u8]>,
1367 ) -> Result<Self, SigningKeyError> {
1368 let key = P521SigningKey::from_pkcs8_encrypted_der(private_key_der, password)
1369 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
1370 Ok(Self { key })
1371 }
1372}
1373
1374impl SigningKey for EcdsaP521SigningKey {
1375 fn sign(
1376 &self,
1377 algorithm: SignatureAlgorithm,
1378 canonical_signed_info: &[u8],
1379 ) -> Result<Vec<u8>, SigningKeyError> {
1380 self.sign_with_provider(
1381 crate::provider::default_provider(),
1382 algorithm,
1383 canonical_signed_info,
1384 )
1385 }
1386
1387 fn sign_with_provider(
1388 &self,
1389 provider: &dyn crate::provider::CryptoProvider,
1390 algorithm: SignatureAlgorithm,
1391 canonical_signed_info: &[u8],
1392 ) -> Result<Vec<u8>, SigningKeyError> {
1393 sign_ecdsa_with_provider::<P521Signature, _>(
1394 &self.key,
1395 provider,
1396 algorithm,
1397 canonical_signed_info,
1398 )
1399 }
1400
1401 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
1402 ecdsa_public_key_info(self.key.verifying_key(), EC_P521_OID)
1403 }
1404}
1405
1406#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1408pub enum SignatureTemplateSelection {
1409 FirstDescendant,
1411 #[default]
1413 LastDescendant,
1414}
1415
1416impl SignatureTemplateSelection {
1417 const fn target(self) -> SigningSignatureTarget {
1418 match self {
1419 Self::FirstDescendant => SigningSignatureTarget::First,
1420 Self::LastDescendant => SigningSignatureTarget::Last,
1421 }
1422 }
1423}
1424
1425pub struct SignContext<'a> {
1427 signing_key: &'a dyn SigningKey,
1428 key_info_writer: Option<&'a dyn KeyInfoWriter>,
1429 start_node_id: Option<&'a str>,
1430 id_attributes: &'a [crate::IdAttributeRegistration],
1431 external_resources: Option<&'a HashMap<String, Vec<u8>>>,
1432 template_selection: SignatureTemplateSelection,
1433 policy: crate::policy::SigningPolicy,
1434 provider: &'a dyn crate::provider::CryptoProvider,
1435 xml_backend: crate::XmlBackend,
1436}
1437
1438impl<'a> SignContext<'a> {
1439 pub fn new(signing_key: &'a dyn SigningKey) -> Self {
1441 Self {
1442 signing_key,
1443 key_info_writer: None,
1444 start_node_id: None,
1445 id_attributes: &[],
1446 external_resources: None,
1447 template_selection: SignatureTemplateSelection::default(),
1448 policy: crate::policy::SigningPolicy::default(),
1449 provider: crate::provider::default_provider(),
1450 xml_backend: crate::XmlBackend::default(),
1451 }
1452 }
1453
1454 #[must_use]
1456 pub fn policy(mut self, policy: crate::policy::SigningPolicy) -> Self {
1457 self.policy = policy;
1458 self
1459 }
1460
1461 #[must_use]
1463 pub fn provider(mut self, provider: &'a dyn crate::provider::CryptoProvider) -> Self {
1464 self.provider = provider;
1465 self
1466 }
1467
1468 #[must_use]
1470 pub fn xml_backend(mut self, backend: crate::XmlBackend) -> Self {
1471 self.xml_backend = backend;
1472 self
1473 }
1474
1475 fn document_parse_settings(&self) -> DocumentParseSettings {
1476 DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources)
1477 .with_backend(self.xml_backend)
1478 }
1479
1480 #[must_use]
1482 pub fn key_info_writer(mut self, writer: &'a dyn KeyInfoWriter) -> Self {
1483 self.key_info_writer = Some(writer);
1484 self
1485 }
1486
1487 #[must_use]
1492 pub fn start_node_id(mut self, id: &'a str) -> Self {
1493 self.start_node_id = Some(id);
1494 self
1495 }
1496
1497 #[must_use]
1503 pub fn signature_template_selection(mut self, selection: SignatureTemplateSelection) -> Self {
1504 self.template_selection = selection;
1505 self
1506 }
1507
1508 #[must_use]
1510 pub fn id_attributes(mut self, registrations: &'a [crate::IdAttributeRegistration]) -> Self {
1511 self.id_attributes = registrations;
1512 self
1513 }
1514
1515 #[must_use]
1522 pub fn external_resources(mut self, resources: &'a HashMap<String, Vec<u8>>) -> Self {
1523 self.external_resources = Some(resources);
1524 self
1525 }
1526
1527 #[must_use]
1533 pub fn xpath_here_semantics(mut self, semantics: XPathHereSemantics) -> Self {
1534 self.policy.transforms.xpath_here_semantics = semantics;
1535 self
1536 }
1537
1538 pub fn sign_template(&self, xml: &str) -> Result<String, SigningError> {
1547 self.policy.validate()?;
1548 self.policy.resources.validate_xml_document_len(xml.len())?;
1549 let mut budgets = SigningOperationBudgets::from_resources_with_backend(
1550 &self.policy.resources,
1551 self.xml_backend,
1552 );
1553 let mut document = XmlDocument::parse_with_settings_and_budget(
1554 xml.to_owned(),
1555 self.document_parse_settings(),
1556 budgets.transforms.xml_parse_work(),
1557 )
1558 .map_err(|error| match owned_document_policy_violation(error) {
1559 Ok(error) => SigningError::Policy(error),
1560 Err(XmlDocumentError::Parse(error)) => {
1561 SigningError::Digest(SigningDigestError::XmlParse(error))
1562 }
1563 Err(error) => SigningError::Document(error),
1564 })?;
1565 self.validate_owned_document_input(&document)?;
1566 self.sign_document_in_place(&mut document, &mut budgets)?;
1567 Ok(document.into_xml())
1568 }
1569
1570 pub fn sign_document(&self, document: &mut XmlDocument) -> Result<(), SigningError> {
1575 self.validate_owned_document_input(document)?;
1576 let mut budgets = SigningOperationBudgets::from_resources_with_backend(
1577 &self.policy.resources,
1578 self.xml_backend,
1579 );
1580 let mut staged = document
1581 .staged_copy_with_budget(
1582 self.document_parse_settings(),
1583 budgets.transforms.xml_parse_work(),
1584 )
1585 .map_err(map_owned_document_mutation_error)?;
1586 self.sign_document_in_place(&mut staged, &mut budgets)?;
1587 commit_signed_staged(document, staged, &self.policy)
1588 }
1589
1590 fn sign_document_in_place(
1591 &self,
1592 document: &mut XmlDocument,
1593 budgets: &mut SigningOperationBudgets,
1594 ) -> Result<(), SigningError> {
1595 let target_signature = document.with_view(|view| {
1596 signing_signature_index(
1597 view.document(),
1598 self.start_node_id,
1599 self.id_attributes,
1600 self.template_selection,
1601 )
1602 })?;
1603 self.policy.resources.validate_key_candidates(1)?;
1604 self.sign_template_at_index_with_budgets(document, target_signature, budgets)?;
1605 Ok(())
1606 }
1607
1608 fn validate_owned_document_input(&self, document: &XmlDocument) -> Result<(), SigningError> {
1609 self.policy.validate()?;
1610 document.validate_operation_policy(&self.policy.xml, &self.policy.resources)?;
1611 if let Some(resources) = self.external_resources {
1612 validate_external_resource_map(
1613 resources,
1614 self.policy.resources.max_external_resource_bytes,
1615 self.policy.resources.max_external_resource_total_bytes,
1616 )
1617 .map_err(map_signing_external_resource_map_error)?;
1618 }
1619 Ok(())
1620 }
1621
1622 fn sign_template_at_index_with_budgets(
1623 &self,
1624 document: &mut XmlDocument,
1625 target_signature: usize,
1626 budgets: &mut SigningOperationBudgets,
1627 ) -> Result<(), SigningError> {
1628 document.with_view(|view| {
1629 let signature = find_signing_signature_node(
1630 view.document(),
1631 SigningSignatureTarget::Index(target_signature),
1632 )?;
1633 parse_signature_children(signature)
1634 .map_err(|error| SigningDigestError::InvalidStructure(error.to_string()))?;
1635 validate_signing_signed_info_methods(signature, &self.policy)?;
1636 Ok::<_, SigningError>(())
1637 })?;
1638 let transform_options = TransformOptions::default()
1639 .allow_internal_dtd(self.policy.xml.allow_internal_dtd)
1640 .xpath_here_semantics(self.policy.transforms.xpath_here_semantics);
1641 let external_resources = ExternalResourceContext::new(
1642 self.external_resources,
1643 self.policy.resources.max_external_resource_bytes,
1644 self.policy.resources.max_external_resource_total_bytes,
1645 );
1646 let binding = (document.identity(), document.generation());
1647 let mut operation =
1648 OperationExecutionContext::new(self.policy.clone(), &mut *budgets, Some(binding));
1649 let key_info = operation.add_node(
1650 OperationNodeKind::Key { index: 0 },
1651 OperationStage::Resolve,
1652 None,
1653 );
1654 operation.compile()?;
1655 let key_info_content = operation.run(key_info, || {
1656 let Some(writer) = self.key_info_writer else {
1657 return Ok::<_, SigningError>(None);
1658 };
1659 let key_info_content =
1660 writer.write_key_info_with_provider(self.signing_key, self.provider)?;
1661 self.policy
1664 .resources
1665 .validate_xml_document_len(key_info_content.len())?;
1666 Ok(Some(key_info_content))
1667 })?;
1668 operation.extend();
1669 let c14n_candidate = materialize_second_edition_c14n11_candidate(
1673 document,
1674 target_signature,
1675 &self.policy,
1676 external_resources.is_configured(),
1677 )?;
1678 let normalization = if let Some(candidate) = c14n_candidate {
1679 let mutation =
1680 operation.add_node(OperationNodeKind::Mutation, OperationStage::Resolve, None);
1681 operation.add_dependency(mutation, key_info)?;
1682 operation.compile()?;
1683 operation.run_document_transition(mutation, document, |document, budgets| {
1684 document
1685 .replace_serialized_with_settings(
1686 candidate,
1687 self.document_parse_settings(),
1688 Some(budgets.transforms.xml_parse_work()),
1689 )
1690 .map_err(map_owned_document_mutation_error)
1691 })?;
1692 operation.extend();
1693 Some(mutation)
1694 } else {
1695 None
1696 };
1697 let setup_gate = normalization.unwrap_or(key_info);
1698 let setup_gate = if let Some(key_info_content) = key_info_content {
1699 let mutation =
1700 operation.add_node(OperationNodeKind::Mutation, OperationStage::Resolve, None);
1701 operation.add_dependency(mutation, setup_gate)?;
1702 operation.compile()?;
1703 operation.run_document_transition(mutation, document, |document, budgets| {
1704 let populated = merge_key_info_source_at_index_with_budget(
1707 document.as_xml(),
1708 &key_info_content,
1709 target_signature,
1710 Some(&self.policy),
1711 Some(budgets.transforms.xml_parse_work()),
1712 )?;
1713 self.policy
1714 .resources
1715 .validate_xml_document_len(populated.len())?;
1716 document
1717 .replace_serialized_with_settings(
1718 populated,
1719 self.document_parse_settings(),
1720 Some(budgets.transforms.xml_parse_work()),
1721 )
1722 .map_err(map_owned_document_mutation_error)
1723 })?;
1724 operation.extend();
1725 mutation
1726 } else {
1727 setup_gate
1728 };
1729 let plan_nodes = fill_reference_digest_values_in_dependency_order_with_operation(
1730 document,
1731 transform_options,
1732 self.provider,
1733 &mut operation,
1734 SigningReferenceRequest {
1735 target_signature,
1736 id_attributes: self.id_attributes,
1737 external_resources: &external_resources,
1738 },
1739 Some(setup_gate),
1740 )?;
1741 self.policy
1742 .resources
1743 .validate_xml_document_len(document.as_xml().len())?;
1744 let (algorithm, hmac_output_length_bits, canonical_signed_info) = operation
1745 .run_with_budgets(plan_nodes.canonicalization, |budgets| {
1746 let result =
1747 canonicalize_signed_info(document, &self.policy, budgets, target_signature)?;
1748 budgets
1749 .transforms
1750 .charge_c14n_output(result.2.len())
1751 .map_err(SigningDigestError::Transform)?;
1752 Ok::<_, SigningError>(result)
1753 })?;
1754 self.policy.check_signature_algorithm(algorithm)?;
1755 let expected_signature_len = expected_signature_output_len(
1756 self.signing_key,
1757 algorithm,
1758 &self.policy,
1759 hmac_output_length_bits,
1760 )?;
1761 let projected_signature_len = projected_signature_output_len(
1762 algorithm,
1763 expected_signature_len,
1764 self.policy.ecdsa_signature_value_encoding,
1765 )?;
1766 let encoded_signature_len =
1767 padded_base64_len_for_xml(projected_signature_len, &self.policy)?;
1768 let signature_value_node = document.with_view(|view| {
1769 let signature = find_signing_signature_node(
1770 view.document(),
1771 SigningSignatureTarget::Index(target_signature),
1772 )?;
1773 let signature_value = find_required_child(signature, "SignatureValue")?;
1774 Ok::<_, SigningError>(view.node_identity(signature_value))
1775 })?;
1776 let projected_document_len = document
1777 .projected_content_replacement_len(signature_value_node, encoded_signature_len)?;
1778 self.policy
1779 .resources
1780 .validate_xml_document_len(projected_document_len)?;
1781 let signature_value = operation.run(plan_nodes.crypto, || {
1782 self.provider
1783 .require_capability(crate::provider::ProviderCapability::Sign(algorithm))
1784 .map_err(SigningKeyError::from)?;
1785 let mut signature_value =
1786 self.provider
1787 .sign(self.signing_key, algorithm, &canonical_signed_info)?;
1788 if algorithm.hmac_output_bits().is_some() {
1789 signature_value.truncate(expected_signature_len);
1790 }
1791 validate_signature_output(expected_signature_len, &signature_value)?;
1792 Ok::<_, SigningError>(signature_value)
1793 })?;
1794 let signature_b64 = operation.run(plan_nodes.evidence, || {
1795 let signature_value = encode_signature_output(
1796 algorithm,
1797 signature_value,
1798 self.policy.ecdsa_signature_value_encoding,
1799 )?;
1800 Ok::<_, SigningError>(base64::engine::general_purpose::STANDARD.encode(signature_value))
1801 })?;
1802 operation.run_document_transition(plan_nodes.mutation, document, |document, budgets| {
1803 document
1804 .replace_base64_contents_with_budget(
1805 &[(signature_value_node, signature_b64)],
1806 self.document_parse_settings(),
1807 budgets.transforms.xml_parse_work(),
1808 )
1809 .map_err(map_owned_document_mutation_error)
1810 })?;
1811 self.policy
1812 .resources
1813 .validate_xml_document_len(document.as_xml().len())?;
1814 Ok(())
1815 }
1816
1817 pub fn sign_with_builder(
1820 &self,
1821 xml: &str,
1822 builder: &SignatureBuilder,
1823 ) -> Result<String, SigningError> {
1824 self.policy.validate()?;
1825 self.policy.resources.validate_xml_document_len(xml.len())?;
1826 let mut budgets = SigningOperationBudgets::from_resources_with_backend(
1827 &self.policy.resources,
1828 self.xml_backend,
1829 );
1830 let mut document = XmlDocument::parse_with_settings_and_budget(
1831 xml.to_owned(),
1832 self.document_parse_settings(),
1833 budgets.transforms.xml_parse_work(),
1834 )
1835 .map_err(|error| match owned_document_policy_violation(error) {
1836 Ok(error) => SigningError::Policy(error),
1837 Err(XmlDocumentError::Parse(error)) => {
1838 SigningError::XmlMutation(XmlMutationError::XmlParse(error))
1839 }
1840 Err(error) => SigningError::Document(error),
1841 })?;
1842 self.validate_owned_document_input(&document)?;
1843 self.sign_document_with_builder_in_place(&mut document, builder, &mut budgets)?;
1844 Ok(document.into_xml())
1845 }
1846
1847 pub fn sign_document_with_builder(
1849 &self,
1850 document: &mut XmlDocument,
1851 builder: &SignatureBuilder,
1852 ) -> Result<(), SigningError> {
1853 self.validate_owned_document_input(document)?;
1854 let mut budgets = SigningOperationBudgets::from_resources_with_backend(
1855 &self.policy.resources,
1856 self.xml_backend,
1857 );
1858 let mut staged = document
1859 .staged_copy_with_budget(
1860 self.document_parse_settings(),
1861 budgets.transforms.xml_parse_work(),
1862 )
1863 .map_err(map_owned_document_mutation_error)?;
1864 self.sign_document_with_builder_in_place(&mut staged, builder, &mut budgets)?;
1865 commit_signed_staged(document, staged, &self.policy)
1866 }
1867
1868 fn sign_document_with_builder_in_place(
1869 &self,
1870 document: &mut XmlDocument,
1871 builder: &SignatureBuilder,
1872 budgets: &mut SigningOperationBudgets,
1873 ) -> Result<(), SigningError> {
1874 self.policy.resources.validate_key_candidates(1)?;
1875 let expected_signature_len = expected_signature_output_len(
1876 self.signing_key,
1877 builder.signature_method(),
1878 &self.policy,
1879 None,
1880 )?;
1881 let template = builder.build_template_with_policy_for_signature_output(
1882 &self.policy,
1883 expected_signature_len,
1884 &budgets.transforms,
1885 &mut budgets.xpath_parse,
1886 )?;
1887 let signature_parent = if let Some(id) = self.start_node_id {
1888 document.with_view(|view| {
1889 let start = signing_start_node(view.document(), id, self.id_attributes)?;
1890 Ok::<_, SigningError>(view.node_identity(start))
1891 })?
1892 } else {
1893 document.with_view(|view| view.root_element())
1894 };
1895 let projected_document_len =
1896 document.projected_child_append_len(signature_parent, template.len())?;
1897 self.policy
1898 .resources
1899 .validate_xml_document_len(projected_document_len)?;
1900 let binding = (document.identity(), document.generation());
1901 let mut operation =
1902 OperationExecutionContext::new(self.policy.clone(), &mut *budgets, Some(binding));
1903 let mutation =
1904 operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
1905 operation.compile()?;
1906 operation.run_document_transition(mutation, document, |document, budgets| {
1907 document
1908 .append_generated_child_with_budget(
1909 signature_parent,
1910 &template,
1911 self.document_parse_settings(),
1912 budgets.transforms.xml_parse_work(),
1913 )
1914 .map_err(map_owned_document_mutation_error)
1915 })?;
1916 drop(operation);
1917 self.policy
1918 .resources
1919 .validate_xml_document_len(document.as_xml().len())?;
1920 let target_signature = document.with_view(|view| {
1921 let parent = if let Some(id) = self.start_node_id {
1922 signing_start_node(view.document(), id, self.id_attributes)?
1923 } else {
1924 view.document().root_element()
1925 };
1926 let appended = parent
1927 .children()
1928 .rfind(|node| node.has_tag_name((XMLDSIG_NS, "Signature")))
1929 .ok_or(SigningDigestError::MissingElement {
1930 element: "Signature",
1931 })?;
1932 signature_index(view.document(), appended).map_err(SigningError::from)
1933 })?;
1934 self.sign_template_at_index_with_budgets(document, target_signature, budgets)?;
1935 Ok(())
1936 }
1937}
1938
1939fn projected_signature_output_len(
1940 algorithm: SignatureAlgorithm,
1941 raw_signature_len: usize,
1942 encoding: crate::policy::EcdsaSignatureValueEncoding,
1943) -> Result<usize, SigningError> {
1944 if matches!(
1945 (algorithm, encoding),
1946 (
1947 SignatureAlgorithm::EcdsaSha1
1948 | SignatureAlgorithm::EcdsaSha224
1949 | SignatureAlgorithm::EcdsaSha256
1950 | SignatureAlgorithm::EcdsaSha384
1951 | SignatureAlgorithm::EcdsaSha512,
1952 crate::policy::EcdsaSignatureValueEncoding::XmlSecAsn1Der
1953 )
1954 ) {
1955 return maximum_ecdsa_der_signature_len(raw_signature_len)
1956 .ok_or(SigningKeyError::InvalidPublicKeyInfo.into());
1957 }
1958 Ok(raw_signature_len)
1959}
1960
1961fn map_owned_document_mutation_error(error: XmlDocumentError) -> SigningError {
1962 match owned_document_policy_violation(error) {
1963 Ok(error) => SigningError::Policy(error),
1964 Err(error) => SigningError::Document(error),
1965 }
1966}
1967
1968fn map_owned_document_digest_mutation_error(error: XmlDocumentError) -> SigningDigestError {
1969 match owned_document_policy_violation(error) {
1970 Ok(error) => SigningDigestError::Policy(error),
1971 Err(error) => SigningDigestError::Document(error),
1972 }
1973}
1974
1975fn map_signing_external_resource_map_error(error: ExternalResourceMapError) -> SigningError {
1976 match error {
1977 ExternalResourceMapError::Policy(error) => SigningError::Policy(error),
1978 ExternalResourceMapError::TotalLengthOverflow => {
1979 SigningDigestError::InvalidStructure("external resource total length overflow".into())
1980 .into()
1981 }
1982 }
1983}
1984
1985fn owned_document_policy_violation(
1986 error: XmlDocumentError,
1987) -> Result<crate::policy::PolicyViolation, XmlDocumentError> {
1988 match error {
1989 XmlDocumentError::Policy(error) => Ok(error),
1990 XmlDocumentError::Parse(crate::xml::dom::ParseError::NamespaceBindingLimitReached {
1991 maximum,
1992 actual,
1993 }) => Ok(crate::policy::PolicyViolation::ResourceLimit {
1994 resource: crate::policy::resource_name::XML_NAMESPACE_BINDINGS,
1995 maximum,
1996 actual,
1997 }),
1998 XmlDocumentError::DocumentTooLarge { maximum, actual } => {
1999 Ok(crate::policy::PolicyViolation::ResourceLimit {
2000 resource: crate::policy::resource_name::XML_DOCUMENT,
2001 maximum,
2002 actual,
2003 })
2004 }
2005 XmlDocumentError::DocumentTooDeep { maximum, actual } => {
2006 Ok(crate::policy::PolicyViolation::ResourceLimit {
2007 resource: crate::policy::resource_name::XML_DEPTH,
2008 maximum,
2009 actual,
2010 })
2011 }
2012 XmlDocumentError::ProjectedNodeLimit { maximum } => {
2013 Ok(crate::policy::PolicyViolation::ResourceLimit {
2014 resource: crate::policy::resource_name::XML_NODES,
2015 maximum,
2016 actual: maximum.saturating_add(1),
2017 })
2018 }
2019 error => Err(error),
2020 }
2021}
2022
2023fn encode_signature_output(
2024 algorithm: SignatureAlgorithm,
2025 signature: Vec<u8>,
2026 encoding: crate::policy::EcdsaSignatureValueEncoding,
2027) -> Result<Vec<u8>, SigningError> {
2028 if matches!(
2029 (algorithm, encoding),
2030 (
2031 SignatureAlgorithm::EcdsaSha1
2032 | SignatureAlgorithm::EcdsaSha224
2033 | SignatureAlgorithm::EcdsaSha256
2034 | SignatureAlgorithm::EcdsaSha384
2035 | SignatureAlgorithm::EcdsaSha512,
2036 crate::policy::EcdsaSignatureValueEncoding::XmlSecAsn1Der
2037 )
2038 ) {
2039 return encode_ecdsa_signature_as_der(&signature)
2040 .ok_or(SigningKeyError::InvalidPublicKeyInfo.into());
2041 }
2042 Ok(signature)
2043}
2044
2045#[derive(Debug, Clone)]
2046struct SigningReference {
2047 uri: String,
2048 origin_node_id: NodeId,
2049 transforms: Vec<Transform>,
2050 digest_method: DigestAlgorithm,
2051 digest_value_range: Range<usize>,
2052 digest_value_node_id: NodeId,
2053}
2054
2055struct SigningOperationBudgets {
2056 transforms: TransformExecutionBudget,
2057 xpath_parse: XPathSignatureParseBudget,
2058}
2059
2060struct SigningReferenceRequest<'a, 'resources> {
2061 target_signature: usize,
2062 id_attributes: &'a [crate::IdAttributeRegistration],
2063 external_resources: &'a ExternalResourceContext<'resources>,
2064}
2065
2066struct SigningPlanNodes {
2067 canonicalization: OperationNodeId,
2068 crypto: OperationNodeId,
2069 evidence: OperationNodeId,
2070 mutation: OperationNodeId,
2071}
2072
2073impl SigningOperationBudgets {
2074 fn from_resources(resources: &crate::policy::ResourcePolicy) -> Self {
2075 Self {
2076 transforms: TransformExecutionBudget::from_resources(resources),
2077 xpath_parse: XPathSignatureParseBudget::from_resources(resources),
2078 }
2079 }
2080
2081 fn from_resources_with_backend(
2082 resources: &crate::policy::ResourcePolicy,
2083 backend: crate::XmlBackend,
2084 ) -> Self {
2085 Self {
2086 transforms: TransformExecutionBudget::from_resources(resources)
2087 .with_xml_backend(backend),
2088 xpath_parse: XPathSignatureParseBudget::from_resources(resources),
2089 }
2090 }
2091}
2092
2093impl Default for SigningOperationBudgets {
2094 fn default() -> Self {
2095 Self::from_resources(&crate::policy::ResourcePolicy::default())
2096 }
2097}
2098
2099pub fn compute_reference_digest_values(
2106 xml: &str,
2107) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2108 let execution_budget = TransformExecutionBudget::default();
2109 compute_reference_digest_values_with_options(
2110 xml,
2111 TransformOptions::default(),
2112 None,
2113 crate::provider::default_provider(),
2114 &execution_budget,
2115 None,
2116 &[],
2117 )
2118}
2119
2120fn compute_reference_digest_values_with_options(
2121 xml: &str,
2122 transform_options: TransformOptions,
2123 policy: Option<&crate::policy::SigningPolicy>,
2124 provider: &dyn crate::provider::CryptoProvider,
2125 execution_budget: &TransformExecutionBudget,
2126 target_signature: Option<usize>,
2127 id_attributes: &[crate::IdAttributeRegistration],
2128) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2129 let (prepared, target_signature) =
2130 prepare_reference_digest_input(xml, policy, execution_budget, target_signature)?;
2131 compute_prepared_reference_digest_values_with_options(
2132 prepared.as_ref(),
2133 transform_options,
2134 policy,
2135 provider,
2136 execution_budget,
2137 target_signature,
2138 id_attributes,
2139 )
2140}
2141
2142fn compute_prepared_reference_digest_values_with_options(
2143 xml: &str,
2144 transform_options: TransformOptions,
2145 policy: Option<&crate::policy::SigningPolicy>,
2146 provider: &dyn crate::provider::CryptoProvider,
2147 execution_budget: &TransformExecutionBudget,
2148 target_signature: usize,
2149 id_attributes: &[crate::IdAttributeRegistration],
2150) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2151 let resource_policy = policy
2152 .map(|policy| &policy.resources)
2153 .cloned()
2154 .unwrap_or_default();
2155 let external_resources = ExternalResourceContext::new(
2156 None,
2157 resource_policy.max_external_resource_bytes,
2158 resource_policy.max_external_resource_total_bytes,
2159 );
2160 let doc = parse_signing_document(
2161 xml,
2162 policy,
2163 execution_budget.xml_parse_work(),
2164 crate::XmlBackend::default(),
2165 )?;
2166 let signature =
2167 find_signing_signature_node(&doc, SigningSignatureTarget::Index(target_signature))?;
2168 let signed_info = find_required_child(signature, "SignedInfo")?;
2169 let references = parse_signing_references(signed_info)?;
2170 validate_signing_references(&references, references.len(), policy, false)?;
2171 compute_signing_reference_digests(
2172 &doc,
2173 signature,
2174 references,
2175 transform_options,
2176 provider,
2177 execution_budget,
2178 SigningUriResolution {
2179 id_attributes,
2180 same_document_id_semantics: policy.map_or(
2181 crate::policy::SameDocumentIdSemantics::Specification,
2182 |policy| policy.transforms.same_document_id_semantics,
2183 ),
2184 external_resources: &external_resources,
2185 },
2186 )
2187}
2188
2189fn prepare_reference_digest_input<'a>(
2190 xml: &'a str,
2191 policy: Option<&crate::policy::SigningPolicy>,
2192 execution_budget: &TransformExecutionBudget,
2193 target_signature: Option<usize>,
2194) -> Result<(Cow<'a, str>, usize), SigningDigestError> {
2195 let default_policy = crate::policy::SigningPolicy::default();
2196 let effective_policy = policy.unwrap_or(&default_policy);
2197 let settings =
2198 DocumentParseSettings::from_policy(&effective_policy.xml, &effective_policy.resources);
2199 let document = XmlDocument::parse_with_settings_and_budget(
2200 xml.to_owned(),
2201 settings,
2202 execution_budget.xml_parse_work(),
2203 )
2204 .map_err(map_owned_document_digest_mutation_error)?;
2205 let target_signature = if let Some(target_signature) = target_signature {
2206 target_signature
2207 } else {
2208 document.with_view(|view| {
2209 let selected =
2210 find_signing_signature_node(view.document(), SigningSignatureTarget::Last)?;
2211 signature_index(view.document(), selected)
2212 })?
2213 };
2214 document.with_view(|view| {
2217 let signature = find_signing_signature_node(
2218 view.document(),
2219 SigningSignatureTarget::Index(target_signature),
2220 )?;
2221 let signed_info = find_required_child(signature, "SignedInfo")?;
2222 let references = parse_signing_references(signed_info)?;
2223 validate_signing_references(&references, references.len(), policy, false)
2224 })?;
2225 let materialized = materialize_second_edition_c14n11_candidate(
2226 &document,
2227 target_signature,
2228 effective_policy,
2229 false,
2230 )?;
2231 Ok((
2232 materialized.map_or(Cow::Borrowed(xml), Cow::Owned),
2233 target_signature,
2234 ))
2235}
2236
2237fn fill_reference_digest_values_in_dependency_order_with_operation(
2238 document: &mut XmlDocument,
2239 transform_options: TransformOptions,
2240 provider: &dyn crate::provider::CryptoProvider,
2241 operation: &mut OperationExecutionContext<
2242 crate::policy::SigningPolicy,
2243 &mut SigningOperationBudgets,
2244 >,
2245 request: SigningReferenceRequest<'_, '_>,
2246 setup_gate: Option<OperationNodeId>,
2247) -> Result<SigningPlanNodes, SigningDigestError> {
2248 let policy = operation.policy().clone();
2249 let reference_limit = policy
2250 .resources
2251 .max_references
2252 .min(MAX_REFERENCES_PER_SIGNATURE);
2253 let process_manifests =
2254 policy.manifest_processing == crate::policy::ManifestProcessing::Process;
2255 let (signed_info_references, manifest_references) = {
2256 let budgets = operation.budgets_mut();
2257 document.with_view(|view| {
2258 let signature = find_signing_signature_node(
2259 view.document(),
2260 SigningSignatureTarget::Index(request.target_signature),
2261 )?;
2262 let signed_info = find_required_child(signature, "SignedInfo")?;
2263 let signed_info_references =
2264 parse_signing_references_with_budget(signed_info, &mut budgets.xpath_parse)?;
2265 validate_signing_references(
2266 &signed_info_references,
2267 signed_info_references.len(),
2268 Some(&policy),
2269 request.external_resources.is_configured(),
2270 )?;
2271 let manifest_references = if process_manifests {
2272 parse_signing_manifest_references(
2273 signature,
2274 &mut budgets.xpath_parse,
2275 reference_limit.saturating_sub(signed_info_references.len()),
2276 reference_limit,
2277 )?
2278 } else {
2279 Vec::new()
2280 };
2281 Ok::<_, SigningDigestError>((signed_info_references, manifest_references))
2282 })?
2283 };
2284 let total_references = signed_info_references
2285 .len()
2286 .checked_add(manifest_references.len())
2287 .ok_or_else(|| SigningDigestError::InvalidStructure("reference count overflow".into()))?;
2288 validate_signing_references(
2289 &manifest_references,
2290 total_references,
2291 Some(&policy),
2292 request.external_resources.is_configured(),
2293 )?;
2294 let placeholder = "AA==";
2295 let analysis_replacements = document.with_view(|view| {
2299 let signature = find_signing_signature_node(
2300 view.document(),
2301 SigningSignatureTarget::Index(request.target_signature),
2302 )?;
2303 let signature_value = find_required_child(signature, "SignatureValue")?;
2304 let mut replacements = signed_info_references
2305 .iter()
2306 .chain(&manifest_references)
2307 .map(|reference| {
2308 (
2309 view.node_identity_by_id(reference.digest_value_node_id),
2310 placeholder.to_owned(),
2311 )
2312 })
2313 .collect::<Vec<_>>();
2314 replacements.push((view.node_identity(signature_value), placeholder.to_owned()));
2315 Ok::<_, SigningDigestError>(replacements)
2316 })?;
2317 let analysis_xml = document
2318 .project_base64_contents(
2319 &analysis_replacements,
2320 policy.resources.max_xml_document_bytes,
2321 )
2322 .map_err(map_owned_document_digest_mutation_error)?;
2323 let analysis_doc = parse_signing_document(
2324 &analysis_xml,
2325 Some(&policy),
2326 operation.budgets().transforms.xml_parse_work(),
2327 document.xml_backend(),
2328 )?;
2329 let analysis_signature = find_signing_signature_node(
2330 &analysis_doc,
2331 SigningSignatureTarget::Index(request.target_signature),
2332 )?;
2333 let analysis_signed_info = find_required_child(analysis_signature, "SignedInfo")?;
2334 let mut analysis_references = parse_signing_references_with_budget(
2335 analysis_signed_info,
2336 &mut operation.budgets_mut().xpath_parse,
2337 )?;
2338 if process_manifests {
2339 analysis_references.extend(parse_signing_manifest_references(
2340 analysis_signature,
2341 &mut operation.budgets_mut().xpath_parse,
2342 reference_limit.saturating_sub(signed_info_references.len()),
2343 reference_limit,
2344 )?);
2345 }
2346 let dependency_plan = reference_dependency_levels(
2347 &analysis_doc,
2348 analysis_signature,
2349 &analysis_references,
2350 transform_options,
2351 &operation.budgets().transforms,
2352 SigningUriResolution {
2353 id_attributes: request.id_attributes,
2354 same_document_id_semantics: policy.transforms.same_document_id_semantics,
2355 external_resources: request.external_resources,
2356 },
2357 )?;
2358 let (node_levels, plan_nodes) =
2359 compile_signing_operation_plan(operation, &dependency_plan, setup_gate)?;
2360 for (level, node_level) in dependency_plan.levels.into_iter().zip(node_levels) {
2361 operation.run_document_transition_batch_with_budgets(
2362 &node_level,
2363 document,
2364 |document, budgets| {
2365 let replacements = document.with_view(|view| {
2366 let current_doc = view.document();
2367 let current_signature = find_signing_signature_node(
2368 current_doc,
2369 SigningSignatureTarget::Index(request.target_signature),
2370 )?;
2371 let current_signed_info = find_required_child(current_signature, "SignedInfo")?;
2372 let current_signed_info_references = parse_signing_references_with_budget(
2373 current_signed_info,
2374 &mut budgets.xpath_parse,
2375 )?;
2376 let current_manifest_references = if process_manifests {
2377 parse_signing_manifest_references(
2378 current_signature,
2379 &mut budgets.xpath_parse,
2380 reference_limit.saturating_sub(signed_info_references.len()),
2381 reference_limit,
2382 )?
2383 } else {
2384 Vec::new()
2385 };
2386 if current_signed_info_references.len() != signed_info_references.len()
2387 || current_manifest_references.len() != manifest_references.len()
2388 {
2389 return Err(SigningDigestError::InvalidStructure(
2390 "signing Reference set changed while filling digests".into(),
2391 ));
2392 }
2393 let mut destinations = Vec::with_capacity(level.len());
2394 let mut level_references = Vec::with_capacity(level.len());
2395 for index in &level {
2396 let reference = if *index < signed_info_references.len() {
2397 current_signed_info_references.get(*index)
2398 } else {
2399 current_manifest_references.get(*index - signed_info_references.len())
2400 }
2401 .ok_or_else(|| {
2402 SigningDigestError::InvalidStructure(
2403 "signing Reference set changed while filling digests".into(),
2404 )
2405 })?;
2406 destinations.push(view.node_identity_by_id(reference.digest_value_node_id));
2407 level_references.push(reference.clone());
2408 }
2409 let computed = compute_signing_reference_digests(
2410 current_doc,
2411 current_signature,
2412 level_references,
2413 transform_options,
2414 provider,
2415 &budgets.transforms,
2416 SigningUriResolution {
2417 id_attributes: request.id_attributes,
2418 same_document_id_semantics: policy
2419 .transforms
2420 .same_document_id_semantics,
2421 external_resources: request.external_resources,
2422 },
2423 )?;
2424 if computed.len() != destinations.len() {
2425 return Err(SigningDigestError::InvalidStructure(
2426 "signing Reference set changed while computing digests".into(),
2427 ));
2428 }
2429 Ok::<_, SigningDigestError>(
2430 destinations
2431 .into_iter()
2432 .zip(computed)
2433 .map(|(target, digest)| (target, digest.digest_value))
2434 .collect::<Vec<_>>(),
2435 )
2436 })?;
2437 document
2438 .replace_base64_contents_with_budget(
2439 &replacements,
2440 DocumentParseSettings::from_policy(&policy.xml, &policy.resources)
2441 .with_backend(document.xml_backend()),
2442 budgets.transforms.xml_parse_work(),
2443 )
2444 .map_err(map_owned_document_digest_mutation_error)
2445 },
2446 )?;
2447 }
2448 Ok(plan_nodes)
2449}
2450
2451#[cfg(test)]
2452fn fill_reference_digest_values_in_dependency_order(
2453 document: &mut XmlDocument,
2454 transform_options: TransformOptions,
2455 policy: &crate::policy::SigningPolicy,
2456 provider: &dyn crate::provider::CryptoProvider,
2457 budgets: &mut SigningOperationBudgets,
2458 target_signature: usize,
2459 id_attributes: &[crate::IdAttributeRegistration],
2460) -> Result<(), SigningDigestError> {
2461 let external_resources = ExternalResourceContext::new(
2462 None,
2463 policy.resources.max_external_resource_bytes,
2464 policy.resources.max_external_resource_total_bytes,
2465 );
2466 let binding = (document.identity(), document.generation());
2467 let mut operation = OperationExecutionContext::new(policy.clone(), budgets, Some(binding));
2468 fill_reference_digest_values_in_dependency_order_with_operation(
2469 document,
2470 transform_options,
2471 provider,
2472 &mut operation,
2473 SigningReferenceRequest {
2474 target_signature,
2475 id_attributes,
2476 external_resources: &external_resources,
2477 },
2478 None,
2479 )
2480 .map(|_| ())
2481}
2482
2483struct SigningDependencyPlan {
2484 dependencies: Vec<HashSet<usize>>,
2485 levels: Vec<Vec<usize>>,
2486}
2487
2488fn reference_dependency_levels(
2489 doc: &Document<'_>,
2490 signature: Node<'_, '_>,
2491 references: &[SigningReference],
2492 transform_options: TransformOptions,
2493 execution_budget: &TransformExecutionBudget,
2494 uri_resolution: SigningUriResolution<'_, '_>,
2495) -> Result<SigningDependencyPlan, SigningDigestError> {
2496 let resolver = uri_resolution.external_resources.bind(
2497 doc,
2498 uri_resolution.id_attributes,
2499 uri_resolution.same_document_id_semantics,
2500 );
2501 let terminal_signature_value_index = references.len();
2502 let signature_value = find_required_child(signature, "SignatureValue")?;
2503 let mut tracked_mutable_nodes = references
2504 .iter()
2505 .enumerate()
2506 .flat_map(|(index, reference)| {
2507 std::iter::once((index, reference.digest_value_node_id)).chain(
2508 doc.get_node(reference.digest_value_node_id)
2509 .into_iter()
2510 .flat_map(|node| node.children())
2511 .filter(|node| node.is_text())
2512 .map(move |node| (index, node.id())),
2513 )
2514 })
2515 .collect::<Vec<_>>();
2516 tracked_mutable_nodes.push((terminal_signature_value_index, signature_value.id()));
2517 tracked_mutable_nodes.extend(
2518 signature_value
2519 .children()
2520 .filter(|node| node.is_text())
2521 .map(|node| (terminal_signature_value_index, node.id())),
2522 );
2523 let analyses = references
2524 .iter()
2525 .map(|reference| {
2526 let origin = signing_reference_origin(doc, reference)?;
2527 let initial_data = resolver.dereference_from_with_budget(
2528 &reference.uri,
2529 origin,
2530 execution_budget.node_set_materialization(),
2531 execution_budget.xml_base_resolution(),
2532 )?;
2533 let output = execute_transforms_with_dependency_nodes(
2534 signature,
2535 initial_data,
2536 &reference.transforms,
2537 transform_options,
2538 execution_budget,
2539 tracked_mutable_nodes.clone(),
2540 )?;
2541 Ok(output.dependencies)
2542 })
2543 .collect::<Result<Vec<_>, SigningDigestError>>()?;
2544 if analyses
2545 .iter()
2546 .any(|dependencies| dependencies.contains(&terminal_signature_value_index))
2547 {
2548 return Err(SigningDigestError::InvalidStructure(
2549 "Reference dependency cycle includes the mutable SignatureValue".into(),
2550 ));
2551 }
2552 let mut remaining_dependencies = analyses.clone();
2553 let mut completed = vec![false; references.len()];
2554 let mut levels = Vec::new();
2555 while completed.iter().any(|done| !done) {
2556 let ready = remaining_dependencies
2557 .iter()
2558 .enumerate()
2559 .filter_map(|(index, dependencies)| {
2560 (!completed[index] && dependencies.is_empty()).then_some(index)
2561 })
2562 .collect::<Vec<_>>();
2563 if ready.is_empty() {
2564 return Err(SigningDigestError::InvalidStructure(
2565 "Manifest Reference digest dependency cycle".into(),
2566 ));
2567 }
2568 for index in &ready {
2569 completed[*index] = true;
2570 }
2571 for dependency_set in &mut remaining_dependencies {
2572 dependency_set.retain(|dependency| !completed[*dependency]);
2573 }
2574 levels.push(ready);
2575 }
2576
2577 debug_assert!(references.iter().all(|reference| {
2581 signature.range().start <= reference.digest_value_range.start
2582 && signature.range().end >= reference.digest_value_range.end
2583 }));
2584 Ok(SigningDependencyPlan {
2585 dependencies: analyses,
2586 levels,
2587 })
2588}
2589
2590fn signing_reference_origin<'doc, 'input>(
2591 doc: &'doc Document<'input>,
2592 reference: &SigningReference,
2593) -> Result<Node<'doc, 'input>, SigningDigestError> {
2594 doc.get_node(reference.origin_node_id).ok_or_else(|| {
2595 SigningDigestError::InvalidStructure(
2596 "signing Reference origin changed while computing digests".into(),
2597 )
2598 })
2599}
2600
2601fn compile_signing_operation_plan(
2602 operation: &mut OperationExecutionContext<
2603 crate::policy::SigningPolicy,
2604 &mut SigningOperationBudgets,
2605 >,
2606 dependency_plan: &SigningDependencyPlan,
2607 setup_gate: Option<OperationNodeId>,
2608) -> Result<(Vec<Vec<OperationNodeId>>, SigningPlanNodes), SigningDigestError> {
2609 let reference_count = dependency_plan.levels.iter().map(Vec::len).sum::<usize>();
2610 let mut digest_nodes = vec![None; reference_count];
2611 for (index, digest_slot) in digest_nodes.iter_mut().enumerate() {
2612 let digest_node = operation.add_node(
2613 OperationNodeKind::Digest { index },
2614 OperationStage::Digest,
2615 None,
2616 );
2617 if let Some(setup_gate) = setup_gate {
2618 operation
2619 .add_dependency(digest_node, setup_gate)
2620 .map_err(map_signing_digest_plan_error)?;
2621 }
2622 *digest_slot = Some(digest_node);
2623 }
2624
2625 let mut node_levels = Vec::with_capacity(dependency_plan.levels.len());
2626 for level in &dependency_plan.levels {
2627 let nodes = level
2628 .iter()
2629 .map(|index| {
2630 digest_nodes
2631 .get(*index)
2632 .and_then(|node| *node)
2633 .ok_or_else(|| {
2634 SigningDigestError::InvalidStructure(
2635 "compiled signing Reference index is unavailable".into(),
2636 )
2637 })
2638 })
2639 .collect::<Result<Vec<_>, _>>()?;
2640 for (index, node) in level.iter().zip(&nodes) {
2641 for dependency_index in &dependency_plan.dependencies[*index] {
2642 let dependency = digest_nodes
2643 .get(*dependency_index)
2644 .and_then(|node| *node)
2645 .ok_or_else(|| {
2646 SigningDigestError::InvalidStructure(
2647 "compiled signing dependency index is unavailable".into(),
2648 )
2649 })?;
2650 operation
2651 .add_dependency(*node, dependency)
2652 .map_err(map_signing_digest_plan_error)?;
2653 }
2654 }
2655 node_levels.push(nodes);
2656 }
2657
2658 let canonicalization = operation.add_node(
2659 OperationNodeKind::Canonicalization,
2660 OperationStage::Canonicalization,
2661 None,
2662 );
2663 for digest in digest_nodes.iter().flatten() {
2664 operation
2665 .add_dependency(canonicalization, *digest)
2666 .map_err(map_signing_digest_plan_error)?;
2667 }
2668 let crypto = operation.add_node(OperationNodeKind::Crypto, OperationStage::Crypto, None);
2669 operation
2670 .add_dependency(crypto, canonicalization)
2671 .map_err(map_signing_digest_plan_error)?;
2672 let evidence = operation.add_node(OperationNodeKind::Evidence, OperationStage::Evidence, None);
2673 operation
2674 .add_dependency(evidence, crypto)
2675 .map_err(map_signing_digest_plan_error)?;
2676 let mutation = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
2677 operation
2678 .add_dependency(mutation, evidence)
2679 .map_err(map_signing_digest_plan_error)?;
2680 operation.compile().map_err(map_signing_digest_plan_error)?;
2681
2682 Ok((
2683 node_levels,
2684 SigningPlanNodes {
2685 canonicalization,
2686 crypto,
2687 evidence,
2688 mutation,
2689 },
2690 ))
2691}
2692
2693fn map_signing_digest_plan_error(error: OperationPlanError) -> SigningDigestError {
2694 SigningDigestError::InvalidStructure(error.to_string())
2695}
2696
2697fn commit_signed_staged(
2698 document: &mut XmlDocument,
2699 staged: XmlDocument,
2700 policy: &crate::policy::SigningPolicy,
2701) -> Result<(), SigningError> {
2702 let mut operation = OperationExecutionContext::new(
2703 policy.clone(),
2704 (),
2705 Some((document.identity(), document.generation())),
2706 );
2707 let mutation = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
2708 operation.compile()?;
2709 operation.run_document_transition(mutation, document, |document, _| {
2710 document
2711 .commit_staged(staged)
2712 .map_err(map_owned_document_mutation_error)
2713 })
2714}
2715
2716fn validate_signing_references(
2717 references: &[SigningReference],
2718 total_references: usize,
2719 policy: Option<&crate::policy::SigningPolicy>,
2720 has_external_resources: bool,
2721) -> Result<(), SigningDigestError> {
2722 if let Some(policy) = policy
2723 && total_references > policy.resources.max_references
2724 {
2725 return Err(crate::policy::PolicyViolation::ResourceLimit {
2726 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
2727 maximum: policy.resources.max_references,
2728 actual: total_references,
2729 }
2730 .into());
2731 }
2732 for reference in references {
2733 if let Some(policy) = policy {
2734 validate_signing_reference_uri(&reference.uri, policy)?;
2735 validate_signing_reference_request(&reference.uri, has_external_resources)?;
2736 }
2737 if let Some(policy) = policy
2738 && reference.transforms.len() > policy.resources.max_transforms_per_reference
2739 {
2740 return Err(crate::policy::PolicyViolation::ResourceLimit {
2741 resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
2742 maximum: policy.resources.max_transforms_per_reference,
2743 actual: reference.transforms.len(),
2744 }
2745 .into());
2746 }
2747 if let Some(policy) = policy {
2748 policy.check_digest_algorithm(reference.digest_method)?;
2749 } else if !reference.digest_method.signing_allowed() {
2750 return Err(SigningDigestError::SigningAlgorithmDisabled {
2751 uri: reference.digest_method.uri(),
2752 });
2753 }
2754 let initial_binary = !reference.uri.is_empty() && !reference.uri.starts_with('#');
2755 validate_signing_transform_policy(
2756 initial_binary,
2757 &reference.transforms,
2758 policy.and_then(|policy| policy.transforms.allowed_algorithms.as_ref()),
2759 )?;
2760 }
2761 Ok(())
2762}
2763
2764fn validate_signing_signed_info_methods(
2765 signature: Node<'_, '_>,
2766 policy: &crate::policy::SigningPolicy,
2767) -> Result<(), SigningDigestError> {
2768 let signed_info = find_required_child(signature, "SignedInfo")?;
2769 let canonicalization_method =
2770 element_children(signed_info)
2771 .next()
2772 .ok_or(SigningDigestError::MissingElement {
2773 element: "CanonicalizationMethod",
2774 })?;
2775 verify_ds_element(canonicalization_method, "CanonicalizationMethod")?;
2776 let algorithm = required_algorithm_attr(canonicalization_method, "CanonicalizationMethod")?;
2777 if policy
2778 .transforms
2779 .allowed_algorithms
2780 .as_ref()
2781 .is_some_and(|allowed| !allowed.contains(algorithm))
2782 {
2783 return Err(crate::policy::PolicyViolation::Algorithm {
2784 operation: "SignedInfo canonicalization",
2785 algorithm: algorithm.to_owned(),
2786 }
2787 .into());
2788 }
2789 Ok(())
2790}
2791
2792struct SigningUriResolution<'a, 'resources> {
2793 id_attributes: &'a [crate::IdAttributeRegistration],
2794 same_document_id_semantics: crate::policy::SameDocumentIdSemantics,
2795 external_resources: &'a ExternalResourceContext<'resources>,
2796}
2797
2798fn compute_signing_reference_digests(
2799 doc: &Document<'_>,
2800 signature: Node<'_, '_>,
2801 references: Vec<SigningReference>,
2802 transform_options: TransformOptions,
2803 provider: &dyn crate::provider::CryptoProvider,
2804 execution_budget: &TransformExecutionBudget,
2805 uri_resolution: SigningUriResolution<'_, '_>,
2806) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
2807 let resolver = uri_resolution.external_resources.bind(
2808 doc,
2809 uri_resolution.id_attributes,
2810 uri_resolution.same_document_id_semantics,
2811 );
2812 references
2813 .into_iter()
2814 .enumerate()
2815 .map(|(index, reference)| {
2816 let origin = signing_reference_origin(doc, &reference)?;
2817 let initial_data = resolver.dereference_from_with_budget(
2818 &reference.uri,
2819 origin,
2820 execution_budget.node_set_materialization(),
2821 execution_budget.xml_base_resolution(),
2822 )?;
2823 let pre_digest = execute_transforms_with_options_and_budget(
2824 signature,
2825 initial_data,
2826 &reference.transforms,
2827 transform_options,
2828 execution_budget,
2829 )?;
2830 let digest = super::compute_digest_with_provider(
2831 provider,
2832 reference.digest_method,
2833 &pre_digest,
2834 )?;
2835 let digest_value = base64::engine::general_purpose::STANDARD.encode(digest);
2836 Ok(ComputedReferenceDigest {
2837 index,
2838 uri: reference.uri,
2839 digest_method: reference.digest_method,
2840 digest_value,
2841 })
2842 })
2843 .collect()
2844}
2845
2846pub fn fill_reference_digest_values(xml: &str) -> Result<String, SigningDigestError> {
2854 let execution_budget = TransformExecutionBudget::default();
2855 fill_reference_digest_values_with_options(
2856 xml,
2857 TransformOptions::default(),
2858 None,
2859 crate::provider::default_provider(),
2860 &execution_budget,
2861 None,
2862 &[],
2863 )
2864}
2865
2866fn fill_reference_digest_values_with_options(
2867 xml: &str,
2868 transform_options: TransformOptions,
2869 policy: Option<&crate::policy::SigningPolicy>,
2870 provider: &dyn crate::provider::CryptoProvider,
2871 execution_budget: &TransformExecutionBudget,
2872 target_signature: Option<usize>,
2873 id_attributes: &[crate::IdAttributeRegistration],
2874) -> Result<String, SigningDigestError> {
2875 let default_policy = crate::policy::SigningPolicy::default();
2876 let effective_policy = policy.unwrap_or(&default_policy);
2877 let (prepared, target_signature) =
2878 prepare_reference_digest_input(xml, policy, execution_budget, target_signature)?;
2879 let digest_values = compute_prepared_reference_digest_values_with_options(
2880 prepared.as_ref(),
2881 transform_options,
2882 Some(effective_policy),
2883 provider,
2884 execution_budget,
2885 target_signature,
2886 id_attributes,
2887 )?
2888 .into_iter()
2889 .map(|digest| digest.digest_value);
2890 Ok(fill_signed_info_digest_values_at_index_with_budget(
2891 prepared.as_ref(),
2892 digest_values,
2893 target_signature,
2894 Some(effective_policy),
2895 Some(execution_budget.xml_parse_work()),
2896 )?)
2897}
2898
2899fn canonicalize_signed_info(
2900 document: &XmlDocument,
2901 policy: &crate::policy::SigningPolicy,
2902 budgets: &mut SigningOperationBudgets,
2903 target_signature: usize,
2904) -> Result<(SignatureAlgorithm, Option<usize>, Vec<u8>), SigningError> {
2905 document.with_view(|view| {
2906 let doc = view.document();
2907 let signature =
2908 find_signing_signature_node(doc, SigningSignatureTarget::Index(target_signature))
2909 .map_err(SigningError::Digest)?;
2910 let signed_info_node =
2911 find_required_child(signature, "SignedInfo").map_err(SigningError::Digest)?;
2912 let signed_info =
2913 parse_signed_info_with_xpath_budget(signed_info_node, &mut budgets.xpath_parse)?;
2914 if policy
2915 .transforms
2916 .allowed_algorithms
2917 .as_ref()
2918 .is_some_and(|allowed| !allowed.contains(signed_info.c14n_method.uri()))
2919 {
2920 return Err(crate::policy::PolicyViolation::Algorithm {
2921 operation: "SignedInfo canonicalization",
2922 algorithm: signed_info.c14n_method.uri().to_owned(),
2923 }
2924 .into());
2925 }
2926 let signed_info_subtree: HashSet<_> = signed_info_node
2927 .descendants()
2928 .map(|node: Node<'_, '_>| node.id())
2929 .collect();
2930 let mut canonical_signed_info = Vec::new();
2931 canonicalize_bounded_with_xml_base_budget(
2932 doc,
2933 Some(&|node| signed_info_subtree.contains(&node.id())),
2934 &signed_info.c14n_method,
2935 budgets.transforms.remaining_c14n_output(),
2936 budgets.transforms.xml_base_resolution(),
2937 &mut canonical_signed_info,
2938 )
2939 .map_err(|error| {
2940 if let Some(violation) = map_c14n_resource_policy_violation(
2941 &error,
2942 crate::policy::resource_name::CANONICALIZED_BYTES,
2943 budgets.transforms.c14n_output_limit(),
2944 ) {
2945 SigningError::Policy(violation)
2946 } else {
2947 SigningError::Canonicalization(error)
2948 }
2949 })?;
2950 Ok((
2951 signed_info.signature_method,
2952 signed_info.hmac_output_length_bits,
2953 canonical_signed_info,
2954 ))
2955 })
2956}
2957
2958const SECOND_EDITION_GENERATION_C14N_URI: &str = "http://www.w3.org/2006/12/xml-c14n11";
2959
2960struct SourceEdit {
2961 range: Range<usize>,
2962 replacement: String,
2963}
2964
2965fn materialize_second_edition_c14n11_candidate(
2966 document: &XmlDocument,
2967 target_signature: usize,
2968 policy: &crate::policy::SigningPolicy,
2969 has_external_resources: bool,
2970) -> Result<Option<String>, SigningDigestError> {
2971 let edits = document.with_view(|view| {
2972 let signature = find_signing_signature_node(
2973 view.document(),
2974 SigningSignatureTarget::Index(target_signature),
2975 )?;
2976 let signed_info = find_required_child(signature, "SignedInfo")?;
2977 let mut reference_nodes = element_children(signed_info)
2978 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Reference")))
2979 .collect::<Vec<_>>();
2980 if policy.manifest_processing == crate::policy::ManifestProcessing::Process {
2981 let reference_limit = policy
2982 .resources
2983 .max_references
2984 .min(MAX_REFERENCES_PER_SIGNATURE);
2985 for manifest in signature
2986 .children()
2987 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Object")))
2988 .flat_map(|object| {
2989 object
2990 .children()
2991 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Manifest")))
2992 })
2993 {
2994 let mut manifest_references = 0usize;
2995 for child in element_children(manifest) {
2996 verify_ds_element(child, "Reference")?;
2997 if reference_nodes.len() == reference_limit {
2998 return Err(crate::policy::PolicyViolation::ResourceLimit {
2999 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
3000 maximum: reference_limit,
3001 actual: reference_limit.saturating_add(1),
3002 }
3003 .into());
3004 }
3005 reference_nodes.push(child);
3006 manifest_references += 1;
3007 }
3008 if manifest_references == 0 {
3009 return Err(SigningDigestError::MissingElement {
3010 element: "Reference",
3011 });
3012 }
3013 }
3014 }
3015 if reference_nodes.len() > policy.resources.max_references {
3016 return Err(crate::policy::PolicyViolation::ResourceLimit {
3017 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
3018 maximum: policy.resources.max_references,
3019 actual: reference_nodes.len(),
3020 }
3021 .into());
3022 }
3023
3024 let mut edits = Vec::new();
3025 for reference_node in reference_nodes {
3026 let structure = parse_signing_reference_structure(reference_node)?;
3027 validate_signing_reference_uri(structure.uri, policy)?;
3028 validate_signing_reference_request(structure.uri, has_external_resources)?;
3029 let digest_uri = required_algorithm_attr(structure.digest_method_node, "DigestMethod")?;
3030 let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
3031 SigningDigestError::UnsupportedAlgorithm {
3032 uri: digest_uri.to_owned(),
3033 }
3034 })?;
3035 policy.check_digest_algorithm(digest_method)?;
3036
3037 let transform_uris = generation_transform_uris(structure.transforms_node)?;
3038 if transform_uris.len() > policy.resources.max_transforms_per_reference {
3039 return Err(crate::policy::PolicyViolation::ResourceLimit {
3040 resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3041 maximum: policy.resources.max_transforms_per_reference,
3042 actual: transform_uris.len(),
3043 }
3044 .into());
3045 }
3046 if let Some(allowed) = policy.transforms.allowed_algorithms.as_ref()
3047 && let Some(disallowed) = transform_uris
3048 .iter()
3049 .find(|algorithm| !allowed.contains(**algorithm))
3050 {
3051 return Err(crate::policy::PolicyViolation::Algorithm {
3052 operation: "signing transform",
3053 algorithm: (*disallowed).to_owned(),
3054 }
3055 .into());
3056 }
3057 let initial_binary = !structure.uri.is_empty() && !structure.uri.starts_with('#');
3058 if generation_chain_produces_binary(initial_binary, &transform_uris) {
3059 continue;
3060 }
3061 if transform_uris.len() >= policy.resources.max_transforms_per_reference {
3062 return Err(crate::policy::PolicyViolation::ResourceLimit {
3063 resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3064 maximum: policy.resources.max_transforms_per_reference,
3065 actual: transform_uris.len().saturating_add(1),
3066 }
3067 .into());
3068 }
3069 if policy
3070 .transforms
3071 .allowed_algorithms
3072 .as_ref()
3073 .is_some_and(|allowed| !allowed.contains(SECOND_EDITION_GENERATION_C14N_URI))
3074 {
3075 return Err(crate::policy::PolicyViolation::Algorithm {
3076 operation: "signing transform",
3077 algorithm: SECOND_EDITION_GENERATION_C14N_URI.to_owned(),
3078 }
3079 .into());
3080 }
3081
3082 let edit = if let Some(transforms_node) = structure.transforms_node {
3083 c14n11_edit_for_transforms(view.xml(), transforms_node)?
3084 } else {
3085 let prefix = reference_node.prefix().unwrap_or_default();
3086 let qualifier = xml_qualifier(prefix);
3087 SourceEdit {
3088 range: structure.digest_method_node.range().start
3089 ..structure.digest_method_node.range().start,
3090 replacement: format!(
3091 "<{qualifier}Transforms><{qualifier}Transform Algorithm=\"{SECOND_EDITION_GENERATION_C14N_URI}\"/></{qualifier}Transforms>"
3092 ),
3093 }
3094 };
3095 edits.push(edit);
3096 }
3097 Ok::<_, SigningDigestError>(edits)
3098 })?;
3099 if edits.is_empty() {
3100 return Ok(None);
3101 }
3102
3103 let projected_len = edits
3104 .iter()
3105 .try_fold(document.as_xml().len(), |total, edit| {
3106 total
3107 .checked_sub(edit.range.len())
3108 .and_then(|value| value.checked_add(edit.replacement.len()))
3109 .ok_or_else(|| {
3110 SigningDigestError::InvalidStructure("document length overflow".into())
3111 })
3112 })?;
3113 policy
3114 .resources
3115 .validate_xml_document_len(projected_len)
3116 .map_err(SigningDigestError::Policy)?;
3117
3118 let mut candidate = document.as_xml().to_owned();
3119 let mut edits = edits;
3120 edits.sort_unstable_by_key(|edit| std::cmp::Reverse(edit.range.start));
3121 for edit in edits {
3122 candidate.replace_range(edit.range, &edit.replacement);
3123 }
3124 Ok(Some(candidate))
3125}
3126
3127fn c14n11_edit_for_transforms(
3128 xml: &str,
3129 transforms_node: Node<'_, '_>,
3130) -> Result<SourceEdit, SigningDigestError> {
3131 let range = transforms_node.range();
3132 let source = xml.get(range.clone()).ok_or_else(|| {
3133 SigningDigestError::InvalidStructure("Transforms source range is unavailable".into())
3134 })?;
3135 let prefix = transforms_node.prefix().unwrap_or_default();
3136 let qualifier = xml_qualifier(prefix);
3137 let transform =
3138 format!("<{qualifier}Transform Algorithm=\"{SECOND_EDITION_GENERATION_C14N_URI}\"/>");
3139 if source.trim_end().ends_with("/>") {
3140 let trimmed = source.trim_end();
3141 let opening = trimmed.strip_suffix("/>").ok_or_else(|| {
3142 SigningDigestError::InvalidStructure(
3143 "self-closing Transforms source is unavailable".into(),
3144 )
3145 })?;
3146 let trailing = &source[trimmed.len()..];
3147 return Ok(SourceEdit {
3148 range,
3149 replacement: format!("{opening}>{transform}</{qualifier}Transforms>{trailing}"),
3150 });
3151 }
3152 let closing = source.rfind("</").ok_or_else(|| {
3153 SigningDigestError::InvalidStructure("Transforms closing tag is unavailable".into())
3154 })?;
3155 let offset = range.start + closing;
3156 Ok(SourceEdit {
3157 range: offset..offset,
3158 replacement: transform,
3159 })
3160}
3161
3162fn generation_transform_uris<'a>(
3163 transforms_node: Option<Node<'a, 'a>>,
3164) -> Result<Vec<&'a str>, SigningDigestError> {
3165 let Some(transforms_node) = transforms_node else {
3166 return Ok(Vec::new());
3167 };
3168 let mut algorithms = Vec::new();
3169 for child in element_children(transforms_node) {
3170 if algorithms.len() == MAX_TRANSFORMS_PER_REFERENCE {
3171 return Err(crate::policy::PolicyViolation::ResourceLimit {
3172 resource: crate::policy::resource_name::REFERENCE_TRANSFORMS,
3173 maximum: MAX_TRANSFORMS_PER_REFERENCE,
3174 actual: MAX_TRANSFORMS_PER_REFERENCE.saturating_add(1),
3175 }
3176 .into());
3177 }
3178 if !child.has_tag_name((XMLDSIG_NS, "Transform")) {
3179 return Err(TransformError::UnsupportedTransform(
3180 "unexpected child element of <ds:Transforms>; only <ds:Transform> is allowed"
3181 .into(),
3182 )
3183 .into());
3184 }
3185 let algorithm = child.attribute("Algorithm").ok_or_else(|| {
3186 TransformError::UnsupportedTransform(
3187 "missing Algorithm attribute on <Transform>".into(),
3188 )
3189 })?;
3190 if algorithm != ENVELOPED_SIGNATURE_URI
3191 && algorithm != BASE64_TRANSFORM_URI
3192 && algorithm != XPATH_TRANSFORM_URI
3193 && algorithm != XPATH_FILTER2_TRANSFORM_URI
3194 && crate::c14n::C14nAlgorithm::from_uri(algorithm).is_none()
3195 {
3196 return Err(TransformError::UnsupportedTransform(algorithm.to_owned()).into());
3197 }
3198 algorithms.push(algorithm);
3199 }
3200 Ok(algorithms)
3201}
3202
3203fn generation_chain_produces_binary(initial_binary: bool, algorithms: &[&str]) -> bool {
3204 algorithms.last().map_or(initial_binary, |algorithm| {
3205 *algorithm == BASE64_TRANSFORM_URI
3206 || crate::c14n::C14nAlgorithm::from_uri(algorithm).is_some()
3207 })
3208}
3209
3210fn xml_qualifier(prefix: &str) -> String {
3211 if prefix.is_empty() {
3212 String::new()
3213 } else {
3214 format!("{prefix}:")
3215 }
3216}
3217
3218fn parse_signing_document<'a>(
3219 xml: &'a str,
3220 policy: Option<&crate::policy::SigningPolicy>,
3221 budget: &XmlParseWorkBudget,
3222 backend: crate::XmlBackend,
3223) -> Result<Document<'a>, SigningDigestError> {
3224 let settings = policy
3225 .map(|policy| DocumentParseSettings::from_policy(&policy.xml, &policy.resources))
3226 .unwrap_or_default()
3227 .with_backend(backend);
3228 super::mutation::parse_with_options_and_budget(xml, settings, Some(budget)).map_err(|error| {
3229 match error.into_policy_violation(settings) {
3230 Ok(error) => SigningDigestError::Policy(error),
3231 Err(XmlDocumentError::Parse(error)) => SigningDigestError::XmlParse(error),
3232 Err(error) => SigningDigestError::Document(error),
3233 }
3234 })
3235}
3236
3237fn parse_private_key_pem(private_key_pem: &str) -> Result<Zeroizing<Vec<u8>>, SigningKeyError> {
3238 let (rest, pem) = x509_parser::pem::parse_x509_pem(private_key_pem.as_bytes())
3239 .map_err(|_| SigningKeyError::InvalidKeyPem)?;
3240 if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
3241 return Err(SigningKeyError::InvalidKeyPem);
3242 }
3243 if pem.label != "PRIVATE KEY" {
3244 return Err(SigningKeyError::InvalidKeyFormat { label: pem.label });
3245 }
3246 Ok(Zeroizing::new(pem.contents))
3247}
3248
3249enum SigningSignatureTarget {
3250 First,
3251 Last,
3252 Index(usize),
3253}
3254
3255fn find_signing_signature_node<'a>(
3256 doc: &'a Document<'a>,
3257 target: SigningSignatureTarget,
3258) -> Result<Node<'a, 'a>, SigningDigestError> {
3259 let mut signatures = doc.descendants().filter(|node| {
3260 node.is_element()
3261 && node.tag_name().name() == "Signature"
3262 && node.tag_name().namespace() == Some(XMLDSIG_NS)
3263 });
3264 match target {
3265 SigningSignatureTarget::First => signatures.next(),
3266 SigningSignatureTarget::Last => signatures.next_back(),
3267 SigningSignatureTarget::Index(index) => signatures.nth(index),
3268 }
3269 .ok_or(SigningDigestError::MissingElement {
3270 element: "Signature",
3271 })
3272}
3273
3274fn signing_signature_index(
3275 doc: &Document<'_>,
3276 start_node_id: Option<&str>,
3277 id_attributes: &[crate::IdAttributeRegistration],
3278 selection: SignatureTemplateSelection,
3279) -> Result<usize, SigningDigestError> {
3280 let selected = if let Some(id) = start_node_id {
3281 let start = signing_start_node(doc, id, id_attributes)?;
3282 let mut signatures = start
3283 .descendants()
3284 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Signature")));
3285 match selection.target() {
3286 SigningSignatureTarget::First => signatures.next(),
3287 SigningSignatureTarget::Last => signatures.next_back(),
3288 SigningSignatureTarget::Index(_) => unreachable!("public selection is not indexed"),
3289 }
3290 .ok_or_else(|| {
3291 SigningDigestError::InvalidStructure(format!(
3292 "selected node subtree has no Signature: {id}"
3293 ))
3294 })?
3295 } else {
3296 find_signing_signature_node(doc, selection.target())?
3297 };
3298 signature_index(doc, selected)
3299}
3300
3301fn signing_start_node<'a>(
3302 doc: &'a Document<'a>,
3303 id: &str,
3304 id_attributes: &[crate::IdAttributeRegistration],
3305) -> Result<Node<'a, 'a>, SigningDigestError> {
3306 UriReferenceResolver::with_id_registrations(doc, id_attributes)
3307 .node_for_id(id)
3308 .ok_or_else(|| {
3309 SigningDigestError::InvalidStructure(format!(
3310 "selected node ID is missing or ambiguous: {id}"
3311 ))
3312 })
3313}
3314
3315fn signature_index(
3316 doc: &Document<'_>,
3317 selected: Node<'_, '_>,
3318) -> Result<usize, SigningDigestError> {
3319 doc.descendants()
3320 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Signature")))
3321 .position(|node| node == selected)
3322 .ok_or(SigningDigestError::MissingElement {
3323 element: "Signature",
3324 })
3325}
3326
3327fn parse_signing_references(
3328 signed_info: Node<'_, '_>,
3329) -> Result<Vec<SigningReference>, SigningDigestError> {
3330 parse_signing_references_with_budget(signed_info, &mut XPathSignatureParseBudget::default())
3331}
3332
3333fn parse_signing_references_with_budget(
3334 signed_info: Node<'_, '_>,
3335 xpath_budget: &mut XPathSignatureParseBudget,
3336) -> Result<Vec<SigningReference>, SigningDigestError> {
3337 verify_ds_element(signed_info, "SignedInfo")?;
3338 let mut children = element_children(signed_info);
3339
3340 let c14n_node = children.next().ok_or(SigningDigestError::MissingElement {
3341 element: "CanonicalizationMethod",
3342 })?;
3343 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
3344 required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
3345
3346 let signature_method_node = children.next().ok_or(SigningDigestError::MissingElement {
3347 element: "SignatureMethod",
3348 })?;
3349 verify_ds_element(signature_method_node, "SignatureMethod")?;
3350 required_algorithm_attr(signature_method_node, "SignatureMethod")?;
3351
3352 let mut references = Vec::new();
3353 for child in children {
3354 verify_ds_element(child, "Reference")?;
3355 if references.len() == MAX_REFERENCES_PER_SIGNATURE {
3356 return Err(SigningDigestError::InvalidStructure(format!(
3357 "SignedInfo contains more than {MAX_REFERENCES_PER_SIGNATURE} Reference elements"
3358 )));
3359 }
3360 references.push(parse_signing_reference(child, xpath_budget)?);
3361 }
3362 if references.is_empty() {
3363 return Err(SigningDigestError::MissingElement {
3364 element: "Reference",
3365 });
3366 }
3367 Ok(references)
3368}
3369
3370fn parse_signing_manifest_references(
3371 signature: Node<'_, '_>,
3372 xpath_budget: &mut XPathSignatureParseBudget,
3373 mut remaining_capacity: usize,
3374 maximum_references: usize,
3375) -> Result<Vec<SigningReference>, SigningDigestError> {
3376 let mut references = Vec::new();
3377 for manifest in signature
3378 .children()
3379 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Object")))
3380 .flat_map(|object| {
3381 object
3382 .children()
3383 .filter(|node| node.has_tag_name((XMLDSIG_NS, "Manifest")))
3384 })
3385 {
3386 let mut manifest_references = 0usize;
3387 for child in element_children(manifest) {
3388 verify_ds_element(child, "Reference")?;
3389 if remaining_capacity == 0 {
3390 return Err(crate::policy::PolicyViolation::ResourceLimit {
3391 resource: crate::policy::resource_name::SIGNATURE_REFERENCES,
3392 maximum: maximum_references,
3393 actual: maximum_references.saturating_add(1),
3394 }
3395 .into());
3396 }
3397 remaining_capacity -= 1;
3398 references.push(parse_signing_reference(child, xpath_budget)?);
3399 manifest_references += 1;
3400 }
3401 if manifest_references == 0 {
3402 return Err(SigningDigestError::MissingElement {
3403 element: "Reference",
3404 });
3405 }
3406 }
3407 Ok(references)
3408}
3409
3410fn parse_signing_reference(
3411 reference_node: Node<'_, '_>,
3412 xpath_budget: &mut XPathSignatureParseBudget,
3413) -> Result<SigningReference, SigningDigestError> {
3414 let structure = parse_signing_reference_structure(reference_node)?;
3415 let transforms = structure.transforms_node.map_or_else(
3416 || Ok(Vec::new()),
3417 |node| parse_transforms_with_budget(node, xpath_budget),
3418 )?;
3419 let digest_uri = required_algorithm_attr(structure.digest_method_node, "DigestMethod")?;
3420 let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
3421 SigningDigestError::UnsupportedAlgorithm {
3422 uri: digest_uri.to_string(),
3423 }
3424 })?;
3425
3426 Ok(SigningReference {
3427 uri: structure.uri.to_owned(),
3428 origin_node_id: reference_node.id(),
3429 transforms,
3430 digest_method,
3431 digest_value_range: structure.digest_value_node.range(),
3432 digest_value_node_id: structure.digest_value_node.id(),
3433 })
3434}
3435
3436struct SigningReferenceStructure<'a> {
3437 uri: &'a str,
3438 transforms_node: Option<Node<'a, 'a>>,
3439 digest_method_node: Node<'a, 'a>,
3440 digest_value_node: Node<'a, 'a>,
3441}
3442
3443fn parse_signing_reference_structure<'a>(
3444 reference_node: Node<'a, 'a>,
3445) -> Result<SigningReferenceStructure<'a>, SigningDigestError> {
3446 let uri = reference_node.attribute("URI").ok_or_else(|| {
3447 SigningDigestError::InvalidStructure("signing Reference must include URI attribute".into())
3448 })?;
3449 let mut children = element_children(reference_node);
3450 let first = children.next().ok_or(SigningDigestError::MissingElement {
3451 element: "DigestMethod",
3452 })?;
3453 let (transforms_node, digest_method_node) = if first.has_tag_name((XMLDSIG_NS, "Transforms")) {
3454 (
3455 Some(first),
3456 children.next().ok_or(SigningDigestError::MissingElement {
3457 element: "DigestMethod",
3458 })?,
3459 )
3460 } else {
3461 (None, first)
3462 };
3463 verify_ds_element(digest_method_node, "DigestMethod")?;
3464 let digest_value_node = children.next().ok_or(SigningDigestError::MissingElement {
3465 element: "DigestValue",
3466 })?;
3467 verify_ds_element(digest_value_node, "DigestValue")?;
3468 if let Some(unexpected) = children.next() {
3469 return Err(SigningDigestError::InvalidStructure(format!(
3470 "unexpected element <{}> after <DigestValue> in <Reference>",
3471 unexpected.tag_name().name()
3472 )));
3473 }
3474 Ok(SigningReferenceStructure {
3475 uri,
3476 transforms_node,
3477 digest_method_node,
3478 digest_value_node,
3479 })
3480}
3481
3482fn find_required_child<'a>(
3483 parent: Node<'a, 'a>,
3484 child_name: &'static str,
3485) -> Result<Node<'a, 'a>, SigningDigestError> {
3486 parent
3487 .children()
3488 .find(|node| {
3489 node.is_element()
3490 && node.tag_name().name() == child_name
3491 && node.tag_name().namespace() == Some(XMLDSIG_NS)
3492 })
3493 .ok_or(SigningDigestError::MissingElement {
3494 element: child_name,
3495 })
3496}
3497
3498fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
3499 node.children().filter(Node::is_element)
3500}
3501
3502fn verify_ds_element(
3503 node: Node<'_, '_>,
3504 expected_name: &'static str,
3505) -> Result<(), SigningDigestError> {
3506 if !node.is_element() {
3507 return Err(SigningDigestError::InvalidStructure(format!(
3508 "expected element <{expected_name}>, got non-element node"
3509 )));
3510 }
3511 let tag = node.tag_name();
3512 if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
3513 return Err(SigningDigestError::InvalidStructure(format!(
3514 "expected <ds:{expected_name}>, got <{}>",
3515 tag.name()
3516 )));
3517 }
3518 Ok(())
3519}
3520
3521fn required_algorithm_attr<'a>(
3522 node: Node<'a, 'a>,
3523 element_name: &'static str,
3524) -> Result<&'a str, SigningDigestError> {
3525 node.attribute("Algorithm").ok_or_else(|| {
3526 SigningDigestError::InvalidStructure(format!(
3527 "missing Algorithm attribute on <{element_name}>"
3528 ))
3529 })
3530}
3531
3532#[cfg(test)]
3533mod error_conversion_tests {
3534 use super::*;
3535 use crate::policy::PolicyViolation;
3536
3537 struct RejectingSigningKey;
3538
3539 impl SigningKey for RejectingSigningKey {
3540 fn sign(
3541 &self,
3542 _algorithm: SignatureAlgorithm,
3543 _canonical_signed_info: &[u8],
3544 ) -> Result<Vec<u8>, SigningKeyError> {
3545 Err(SigningKeyError::SigningFailed)
3546 }
3547
3548 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
3549 Err(SigningKeyError::PublicKeyEncodingFailed)
3550 }
3551 }
3552
3553 struct FixedRsaSigningKey;
3554
3555 impl SigningKey for FixedRsaSigningKey {
3556 fn sign(
3557 &self,
3558 _algorithm: SignatureAlgorithm,
3559 _canonical_signed_info: &[u8],
3560 ) -> Result<Vec<u8>, SigningKeyError> {
3561 Ok(vec![0x5a; 256])
3562 }
3563
3564 fn public_key_info(&self) -> Result<SigningPublicKeyInfo, SigningKeyError> {
3565 Ok(SigningPublicKeyInfo::Rsa {
3566 spki_der: Vec::new(),
3567 modulus: vec![0x80; 256],
3568 exponent: vec![1, 0, 1],
3569 })
3570 }
3571 }
3572
3573 #[test]
3574 fn namespace_exhaustion_preserves_typed_signing_errors() {
3575 let make_error = || {
3577 XmlDocumentError::Parse(crate::xml::dom::ParseError::NamespaceBindingLimitReached {
3578 maximum: 2,
3579 actual: 3,
3580 })
3581 };
3582 assert!(matches!(
3583 owned_document_policy_violation(make_error()),
3584 Ok(PolicyViolation::ResourceLimit {
3585 resource: crate::policy::resource_name::XML_NAMESPACE_BINDINGS,
3586 maximum: 2,
3587 actual: 3,
3588 })
3589 ));
3590 let mut policy = crate::policy::SigningPolicy::default();
3591 policy.resources.max_xml_namespace_bindings = 0;
3592 let context = SignContext::new(&RejectingSigningKey).policy(policy);
3593 let builder = SignatureBuilder::new(
3594 crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3595 SignatureAlgorithm::RsaSha256,
3596 );
3597 let xml = "<root xmlns:p='urn:p'/>";
3598 for result in [
3599 context.sign_template(xml),
3600 context.sign_with_builder(xml, &builder),
3601 ] {
3602 assert!(
3603 matches!(
3604 result,
3605 Err(SigningError::Policy(PolicyViolation::ResourceLimit {
3606 resource: crate::policy::resource_name::XML_NAMESPACE_BINDINGS,
3607 maximum: 0,
3608 actual: 1,
3609 }))
3610 ),
3611 "unexpected signing result: {result:?}"
3612 );
3613 }
3614 }
3615
3616 #[test]
3617 fn signing_error_promotes_every_policy_failure() {
3618 let digest = SigningError::from(SigningDigestError::Policy(PolicyViolation::Algorithm {
3621 operation: "signing",
3622 algorithm: "urn:test:digest".into(),
3623 }));
3624 assert!(matches!(digest, SigningError::Policy(_)));
3625
3626 let mutation = SigningError::from(SigningDigestError::XmlMutation(
3627 XmlMutationError::Policy(PolicyViolation::ResourceLimit {
3628 resource: crate::policy::resource_name::XML_DOCUMENT,
3629 maximum: 1,
3630 actual: 2,
3631 }),
3632 ));
3633 assert!(matches!(mutation, SigningError::Policy(_)));
3634 }
3635
3636 #[test]
3637 fn manifest_reparse_consumes_the_signature_wide_xpath_budget() {
3638 let filter = r#"<xf:XPath xmlns:xf="http://www.w3.org/2002/06/xmldsig-filter2" Filter="intersect">true()</xf:XPath>"#;
3642 let signed_info_transforms = format!(
3643 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>"#,
3644 filter.repeat(64)
3645 );
3646 let signed_info_references = (0..62)
3647 .map(|index| {
3648 let extra = if index == 0 {
3649 r#"<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><ds:XPath>true()</ds:XPath></ds:Transform>"#
3650 } else {
3651 ""
3652 };
3653 format!(
3654 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>"##
3655 )
3656 })
3657 .collect::<String>();
3658 let manifest_reference = |id: &str| {
3659 format!(
3660 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>"##
3661 )
3662 };
3663 let xml = format!(
3664 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>"##,
3665 manifest_reference("payload"),
3666 manifest_reference("payload")
3667 );
3668 let policy = crate::policy::SigningPolicy {
3669 manifest_processing: crate::policy::ManifestProcessing::Process,
3670 ..crate::policy::SigningPolicy::default()
3671 };
3672
3673 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3674 let error = fill_reference_digest_values_in_dependency_order(
3675 &mut document,
3676 TransformOptions::default(),
3677 &policy,
3678 crate::provider::default_provider(),
3679 &mut SigningOperationBudgets::default(),
3680 0,
3681 &[],
3682 )
3683 .expect_err("Manifest reparse must not reset the XPath parse budget");
3684
3685 assert!(
3686 matches!(
3687 &error,
3688 SigningDigestError::Transform(TransformError::Policy(
3689 crate::policy::PolicyViolation::ResourceLimit {
3690 resource: "XPath expressions",
3691 ..
3692 }
3693 ))
3694 ),
3695 "expected the shared XPath budget error, got: {error:?}"
3696 );
3697 }
3698
3699 #[test]
3700 fn dependency_levels_share_the_xml_parse_work_budget() {
3701 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>"##;
3705 let policy = crate::policy::SigningPolicy {
3706 manifest_processing: crate::policy::ManifestProcessing::Process,
3707 ..crate::policy::SigningPolicy::default()
3708 };
3709 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3710 let mut budgets = SigningOperationBudgets::from_resources(&policy.resources);
3711
3712 fill_reference_digest_values_in_dependency_order(
3713 &mut document,
3714 TransformOptions::default(),
3715 &policy,
3716 crate::provider::default_provider(),
3717 &mut budgets,
3718 0,
3719 &[],
3720 )
3721 .expect("the default cumulative budget must cover nested dependencies");
3722 let consumed = budgets.transforms.xml_parse_work().consumed();
3723 assert!(
3724 consumed > xml.len().saturating_mul(6),
3725 "analysis and dependency reparses must all be charged"
3726 );
3727
3728 let mut constrained_policy = policy;
3729 constrained_policy.resources.max_xml_parse_work_bytes = consumed - 1;
3730 let mut constrained_document = XmlDocument::parse(xml).expect("fixture must parse");
3731 let mut constrained_budgets =
3732 SigningOperationBudgets::from_resources(&constrained_policy.resources);
3733 let error = fill_reference_digest_values_in_dependency_order(
3734 &mut constrained_document,
3735 TransformOptions::default(),
3736 &constrained_policy,
3737 crate::provider::default_provider(),
3738 &mut constrained_budgets,
3739 0,
3740 &[],
3741 )
3742 .expect_err("one byte below measured work must fail closed");
3743
3744 assert!(matches!(
3745 error,
3746 SigningDigestError::Policy(PolicyViolation::ResourceLimit {
3747 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3748 maximum,
3749 actual,
3750 }) if maximum == consumed - 1 && actual >= consumed
3751 ));
3752 }
3753
3754 #[test]
3755 fn final_signed_info_parse_consumes_the_signing_xpath_budget() {
3756 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>"##;
3760 let mut policy = crate::policy::SigningPolicy::default();
3761 policy.resources.max_xpath_expressions = 3;
3762
3763 let error = SignContext::new(&RejectingSigningKey)
3764 .policy(policy)
3765 .sign_template(xml)
3766 .expect_err("the final SignedInfo parse must not reset the XPath budget");
3767
3768 assert!(
3769 matches!(
3770 error,
3771 SigningError::Policy(PolicyViolation::ResourceLimit {
3772 resource: crate::policy::resource_name::XPATH_EXPRESSIONS,
3773 maximum: 3,
3774 ..
3775 })
3776 ),
3777 "expected the shared XPath parse budget error, got: {error:?}"
3778 );
3779 }
3780
3781 #[test]
3782 fn signing_initial_parse_consumes_the_operation_xml_parse_budget() {
3783 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>"##;
3787 let mut policy = crate::policy::SigningPolicy::default();
3788 policy.resources.max_xml_parse_work_bytes = 0;
3789
3790 let error = SignContext::new(&RejectingSigningKey)
3791 .policy(policy)
3792 .sign_template(xml)
3793 .expect_err("a zero parse-work budget must reject the initial parse");
3794
3795 assert!(matches!(
3796 error,
3797 SigningError::Policy(PolicyViolation::ResourceLimit {
3798 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3799 maximum: 0,
3800 actual,
3801 }) if actual == xml.len()
3802 ));
3803 }
3804
3805 #[test]
3806 fn signing_string_entry_point_enforces_policy_depth() {
3807 let mut policy = crate::policy::SigningPolicy::default();
3810 policy.resources.max_xml_depth = 2;
3811 let xml = "<root><child><leaf/></child></root>";
3812
3813 assert!(matches!(
3814 SignContext::new(&RejectingSigningKey)
3815 .policy(policy)
3816 .sign_template(xml),
3817 Err(SigningError::Policy(PolicyViolation::ResourceLimit {
3818 resource: crate::policy::resource_name::XML_DEPTH,
3819 maximum: 2,
3820 actual: 3,
3821 }))
3822 ));
3823 }
3824
3825 #[test]
3826 fn builder_append_reports_policy_depth() {
3827 let mut policy = crate::policy::SigningPolicy::default();
3830 policy.resources.max_xml_depth = 4;
3831 let builder = SignatureBuilder::new(
3832 crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3833 SignatureAlgorithm::RsaSha256,
3834 )
3835 .add_reference(crate::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256).uri(""));
3836
3837 let result = SignContext::new(&FixedRsaSigningKey)
3838 .policy(policy)
3839 .sign_with_builder("<root/>", &builder);
3840 assert!(
3841 matches!(
3842 result,
3843 Err(SigningError::Policy(PolicyViolation::ResourceLimit {
3844 resource: crate::policy::resource_name::XML_DEPTH,
3845 maximum: 4,
3846 actual: 5,
3847 }))
3848 ),
3849 "unexpected builder depth result: {result:?}"
3850 );
3851 }
3852
3853 #[test]
3854 fn owned_signing_staged_copies_preserve_policy_errors() {
3855 let mut policy = crate::policy::SigningPolicy::default();
3858 policy.resources.max_xml_parse_work_bytes = 0;
3859 let context = SignContext::new(&RejectingSigningKey).policy(policy);
3860
3861 let mut template_document = XmlDocument::parse("<root/>").expect("fixture must parse");
3862 let template_before = template_document.as_xml().to_owned();
3863 let error = context
3864 .sign_document(&mut template_document)
3865 .expect_err("the staged template copy must exhaust the operation budget");
3866 assert!(matches!(
3867 error,
3868 SigningError::Policy(PolicyViolation::ResourceLimit {
3869 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3870 maximum: 0,
3871 actual,
3872 }) if actual == template_before.len()
3873 ));
3874 assert_eq!(template_document.as_xml(), template_before);
3875 assert_eq!(template_document.generation(), 0);
3876
3877 let mut builder_document = XmlDocument::parse("<root/>").expect("fixture must parse");
3878 let builder_before = builder_document.as_xml().to_owned();
3879 let builder = SignatureBuilder::new(
3880 crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3881 SignatureAlgorithm::RsaSha256,
3882 );
3883 let error = context
3884 .sign_document_with_builder(&mut builder_document, &builder)
3885 .expect_err("the staged builder copy must exhaust the operation budget");
3886 assert!(matches!(
3887 error,
3888 SigningError::Policy(PolicyViolation::ResourceLimit {
3889 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3890 maximum: 0,
3891 actual,
3892 }) if actual == builder_before.len()
3893 ));
3894 assert_eq!(builder_document.as_xml(), builder_before);
3895 assert_eq!(builder_document.generation(), 0);
3896 }
3897
3898 #[test]
3899 fn owned_signing_commits_the_validated_stage_without_reparsing() {
3900 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>"##;
3904 let policy = crate::policy::SigningPolicy::default();
3905 let context = SignContext::new(&FixedRsaSigningKey).policy(policy.clone());
3906 let source = XmlDocument::parse(xml).expect("fixture must parse");
3907 let mut measured = SigningOperationBudgets::from_resources(&policy.resources);
3908 let mut staged = source
3909 .staged_copy_with_budget(
3910 DocumentParseSettings::from_policy(&policy.xml, &policy.resources),
3911 measured.transforms.xml_parse_work(),
3912 )
3913 .expect("staging must parse");
3914 context
3915 .sign_document_in_place(&mut staged, &mut measured)
3916 .expect("staged signing must succeed");
3917 let exact_stage_work = measured.transforms.xml_parse_work().consumed();
3918
3919 let mut constrained_policy = policy;
3920 constrained_policy.resources.max_xml_parse_work_bytes = exact_stage_work;
3921 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
3922 SignContext::new(&FixedRsaSigningKey)
3923 .policy(constrained_policy)
3924 .sign_document(&mut document)
3925 .expect("commit must not parse the validated stage again");
3926
3927 assert_eq!(document.generation(), 1);
3928 assert!(!document.as_xml().contains("<ds:DigestValue/>"));
3929 assert!(!document.as_xml().contains("<ds:SignatureValue/>"));
3930 }
3931
3932 #[test]
3933 fn builder_signing_fits_the_document_to_parse_work_ratio() {
3934 let padding = "x".repeat(64 * 1024);
3940 let xml = format!("<root><payload Id=\"payload\"/><padding>{padding}</padding></root>");
3941 let builder = SignatureBuilder::new(
3942 crate::c14n::C14nAlgorithm::new(crate::c14n::C14nMode::Exclusive1_0, false),
3943 SignatureAlgorithm::RsaSha256,
3944 )
3945 .add_reference(
3946 crate::xmldsig::ReferenceBuilder::new(DigestAlgorithm::Sha256).uri("#payload"),
3947 );
3948 let maximum_document_bytes = xml.len() + 4 * 1024;
3949 let mut policy = crate::policy::SigningPolicy::default();
3950 policy.resources.max_xml_document_bytes = maximum_document_bytes;
3951 policy.resources.max_xml_parse_work_bytes =
3952 maximum_document_bytes * crate::hard_limits::XML_PARSE_WORK_PASS_CEILING;
3953
3954 let mut measurement_policy = policy.clone();
3955 measurement_policy.resources.max_xml_parse_work_bytes =
3956 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING;
3957 let measurement_context =
3958 SignContext::new(&FixedRsaSigningKey).policy(measurement_policy.clone());
3959 let mut measurement_budgets =
3960 SigningOperationBudgets::from_resources(&measurement_policy.resources);
3961 let mut measurement_document = XmlDocument::parse_with_settings_and_budget(
3962 xml.clone(),
3963 DocumentParseSettings::from_policy(
3964 &measurement_policy.xml,
3965 &measurement_policy.resources,
3966 ),
3967 measurement_budgets.transforms.xml_parse_work(),
3968 )
3969 .expect("measurement input must parse");
3970 measurement_context
3971 .sign_document_with_builder_in_place(
3972 &mut measurement_document,
3973 &builder,
3974 &mut measurement_budgets,
3975 )
3976 .expect("measurement signing must succeed");
3977 let consumed = measurement_budgets.transforms.xml_parse_work().consumed();
3978 assert!(
3979 consumed <= maximum_document_bytes * crate::hard_limits::XML_PARSE_WORK_PASS_CEILING,
3980 "builder signing consumed {consumed} bytes for a {maximum_document_bytes}-byte ceiling"
3981 );
3982
3983 let signed = SignContext::new(&FixedRsaSigningKey)
3984 .policy(policy.clone())
3985 .sign_with_builder(&xml, &builder)
3986 .expect("string builder signing must fit the advertised parse-work ratio");
3987 assert!(signed.contains("DigestValue>"));
3988 assert!(signed.contains("SignatureValue>"));
3989
3990 let mut owned = XmlDocument::parse(&xml).expect("fixture must parse");
3991 SignContext::new(&FixedRsaSigningKey)
3992 .policy(policy)
3993 .sign_document_with_builder(&mut owned, &builder)
3994 .expect("owned builder signing must fit the advertised parse-work ratio");
3995 assert!(owned.as_xml().contains("DigestValue>"));
3996 assert!(owned.as_xml().contains("SignatureValue>"));
3997 }
3998
3999 #[test]
4000 fn dtd_capable_staged_copy_charges_both_parser_passes() {
4001 let xml = "<root/>";
4004 let mut parsing_policy = crate::policy::SigningPolicy::default();
4005 parsing_policy.xml.allow_internal_dtd = true;
4006 let document = XmlDocument::parse_with_policy(xml, &parsing_policy)
4007 .expect("the fixture must retain DTD-capable parse settings");
4008
4009 parsing_policy.resources.max_xml_parse_work_bytes = xml.len();
4010 let budget = XmlParseWorkBudget::from_resources(&parsing_policy.resources);
4011 assert!(matches!(
4012 document.staged_copy_with_budget(DocumentParseSettings::default(), &budget),
4013 Err(XmlDocumentError::Policy(PolicyViolation::ResourceLimit {
4014 resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
4015 maximum,
4016 actual,
4017 })) if maximum == xml.len() && actual == xml.len() * 2
4018 ));
4019 }
4020
4021 #[test]
4022 fn owned_signing_mappers_preserve_document_size_policy_errors() {
4023 let maximum = 8;
4026 let actual = 9;
4027 assert!(matches!(
4028 map_owned_document_mutation_error(XmlDocumentError::DocumentTooLarge {
4029 maximum,
4030 actual,
4031 }),
4032 SigningError::Policy(PolicyViolation::ResourceLimit {
4033 resource: crate::policy::resource_name::XML_DOCUMENT,
4034 maximum: 8,
4035 actual: 9,
4036 })
4037 ));
4038 assert!(matches!(
4039 map_owned_document_digest_mutation_error(XmlDocumentError::DocumentTooLarge {
4040 maximum,
4041 actual,
4042 }),
4043 SigningDigestError::Policy(PolicyViolation::ResourceLimit {
4044 resource: crate::policy::resource_name::XML_DOCUMENT,
4045 maximum: 8,
4046 actual: 9,
4047 })
4048 ));
4049 }
4050
4051 #[test]
4052 fn signing_rejects_xpath_control_dependencies_on_digest_values() {
4053 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>"##;
4057
4058 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4059 let error = fill_reference_digest_values_in_dependency_order(
4060 &mut document,
4061 TransformOptions::default(),
4062 &crate::policy::SigningPolicy::default(),
4063 crate::provider::default_provider(),
4064 &mut SigningOperationBudgets::default(),
4065 0,
4066 &[],
4067 )
4068 .expect_err("mutable XPath control dependencies must fail closed");
4069
4070 assert!(
4071 matches!(
4072 &error,
4073 SigningDigestError::InvalidStructure(message)
4074 if message.contains("dependency cycle")
4075 ),
4076 "expected a dependency-cycle rejection, got: {error:?}"
4077 );
4078 }
4079
4080 #[test]
4081 fn signing_allows_payload_local_xpath_value_predicates() {
4082 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>"##;
4085
4086 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4087 fill_reference_digest_values_in_dependency_order(
4088 &mut document,
4089 TransformOptions::default(),
4090 &crate::policy::SigningPolicy::default(),
4091 crate::provider::default_provider(),
4092 &mut SigningOperationBudgets::default(),
4093 0,
4094 &[],
4095 )
4096 .expect("payload-local XPath predicates must not depend on Signature values");
4097
4098 assert_ne!(document.as_xml(), xml);
4099 }
4100
4101 #[test]
4102 fn signing_rejects_references_that_retain_signature_value() {
4103 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>"##;
4107
4108 let mut document = XmlDocument::parse(xml).expect("fixture must parse");
4109 let error = fill_reference_digest_values_in_dependency_order(
4110 &mut document,
4111 TransformOptions::default(),
4112 &crate::policy::SigningPolicy::default(),
4113 crate::provider::default_provider(),
4114 &mut SigningOperationBudgets::default(),
4115 0,
4116 &[],
4117 )
4118 .expect_err("a mutable SignatureValue dependency must fail before signing");
4119
4120 assert!(
4121 matches!(
4122 &error,
4123 SigningDigestError::InvalidStructure(message)
4124 if message.contains("SignatureValue") && message.contains("cycle")
4125 ),
4126 "expected a SignatureValue dependency-cycle rejection, got: {error:?}"
4127 );
4128 }
4129
4130 #[test]
4131 fn second_edition_c14n_materialization_handles_both_reference_shapes() {
4132 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>"##;
4135 let document = XmlDocument::parse(xml).expect("fixture must parse");
4136 let candidate = materialize_second_edition_c14n11_candidate(
4137 &document,
4138 0,
4139 &crate::policy::SigningPolicy::default(),
4140 false,
4141 )
4142 .expect("materialization planning must succeed")
4143 .expect("both node-set chains require explicit canonicalization");
4144
4145 assert_eq!(
4146 candidate
4147 .matches(SECOND_EDITION_GENERATION_C14N_URI)
4148 .count(),
4149 3
4150 );
4151 assert!(candidate.contains("<ds:Transforms><ds:Transform"));
4152 XmlDocument::parse(candidate).expect("materialized candidate must remain well-formed");
4153 }
4154
4155 #[test]
4156 fn second_edition_c14n_materialization_is_idempotent_for_binary_chains() {
4157 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>"##;
4160 let document = XmlDocument::parse(xml).expect("fixture must parse");
4161 let candidate = materialize_second_edition_c14n11_candidate(
4162 &document,
4163 0,
4164 &crate::policy::SigningPolicy::default(),
4165 false,
4166 )
4167 .expect("materialization planning must succeed");
4168
4169 assert!(candidate.is_none());
4170 }
4171}