1use crate::core::{Document, NodeId, NodeKind, XmlError, XmlResult};
2
3use super::xmldsig::{
4 element_children, find_signature, optional_attribute, parse_signed_info, required_child,
5 resolve_signature_parent,
6};
7use super::{
8 verify_enveloped, verify_xades_baseline_b_enveloped, verify_xades_bes_enveloped,
9 verify_xades_epes_enveloped, verify_xades_validation_data, DigestAlgorithm, SignatureAlgorithm,
10 SignaturePlacement, SignaturePolicy, SigningProvider, XadesConfig, XadesProfile,
11 XadesValidationDataConfig, XmlDsigConfig, XADES_NAMESPACE_URI,
12 XMLDSIG_SIGNED_PROPERTIES_TYPE_URI,
13};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum SignatureValidationLevel {
18 XmlDsig,
19 XadesBes,
20 XadesEpes(SignaturePolicy),
21 XadesBaselineB { policy: Option<SignaturePolicy> },
22 XadesT,
23 XadesLt,
24 XadesLta,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum SignatureValidationReference {
30 Document,
31 WholeDocument,
32 KeyInfo,
33 SignedProperties,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum SignatureValidationIssueKind {
39 Cryptographic,
40 MissingStructure,
41 MissingExternalMaterial,
42 Algorithm,
43 Policy,
44 Placement,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct SignatureValidationIssue {
50 pub kind: SignatureValidationIssueKind,
51 pub code: String,
52 pub message: String,
53}
54
55impl SignatureValidationIssue {
56 pub fn new(
57 kind: SignatureValidationIssueKind,
58 code: impl Into<String>,
59 message: impl Into<String>,
60 ) -> Self {
61 Self {
62 kind,
63 code: code.into(),
64 message: message.into(),
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct SignatureValidationReport {
72 pub valid: bool,
73 pub xmldsig_valid: Option<bool>,
74 pub xades_valid: Option<bool>,
75 pub required_references_valid: bool,
76 pub algorithms_valid: bool,
77 pub placement_valid: Option<bool>,
78 pub external_material_valid: bool,
79 pub issues: Vec<SignatureValidationIssue>,
80}
81
82impl SignatureValidationReport {
83 fn from_issues(
84 xmldsig_valid: Option<bool>,
85 xades_valid: Option<bool>,
86 placement_valid: Option<bool>,
87 issues: Vec<SignatureValidationIssue>,
88 ) -> Self {
89 let required_references_valid = !issues
90 .iter()
91 .any(|issue| issue.code.starts_with("missing_reference"));
92 let algorithms_valid = !issues
93 .iter()
94 .any(|issue| issue.kind == SignatureValidationIssueKind::Algorithm);
95 let external_material_valid = !issues
96 .iter()
97 .any(|issue| issue.kind == SignatureValidationIssueKind::MissingExternalMaterial);
98 let valid = issues.is_empty()
99 && xmldsig_valid.unwrap_or(false)
100 && xades_valid.unwrap_or(true)
101 && placement_valid.unwrap_or(true);
102
103 Self {
104 valid,
105 xmldsig_valid,
106 xades_valid,
107 required_references_valid,
108 algorithms_valid,
109 placement_valid,
110 external_material_valid,
111 issues,
112 }
113 }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct SignatureValidationProfile {
123 level: SignatureValidationLevel,
124 xades_config: XadesConfig,
125 required_references: Vec<SignatureValidationReference>,
126 allowed_signature_algorithms: Vec<SignatureAlgorithm>,
127 allowed_digest_algorithms: Vec<DigestAlgorithm>,
128 expected_signature_placement: Option<SignaturePlacement>,
129 require_certificate_chain: bool,
130 require_revocation_values: bool,
131}
132
133impl SignatureValidationProfile {
134 pub fn new() -> Self {
135 Self::default()
136 }
137
138 pub fn with_level(mut self, level: SignatureValidationLevel) -> Self {
139 self.level = level;
140 self
141 }
142
143 pub fn with_xades_config(mut self, config: XadesConfig) -> Self {
144 self.xades_config = config;
145 self
146 }
147
148 pub fn require_reference(mut self, reference: SignatureValidationReference) -> Self {
149 self.required_references.push(reference);
150 self
151 }
152
153 pub fn allow_signature_algorithm(mut self, algorithm: SignatureAlgorithm) -> Self {
154 self.allowed_signature_algorithms.push(algorithm);
155 self
156 }
157
158 pub fn allow_digest_algorithm(mut self, algorithm: DigestAlgorithm) -> Self {
159 self.allowed_digest_algorithms.push(algorithm);
160 self
161 }
162
163 pub fn with_expected_signature_placement(mut self, placement: SignaturePlacement) -> Self {
164 self.expected_signature_placement = Some(placement);
165 self
166 }
167
168 pub fn require_certificate_chain(mut self, required: bool) -> Self {
169 self.require_certificate_chain = required;
170 self
171 }
172
173 pub fn require_revocation_values(mut self, required: bool) -> Self {
174 self.require_revocation_values = required;
175 self
176 }
177
178 pub fn validate(
179 &self,
180 document: &Document,
181 provider: &impl SigningProvider,
182 ) -> XmlResult<SignatureValidationReport> {
183 let mut issues = Vec::new();
184 let signature = match find_signature(document) {
185 Ok(signature) => signature,
186 Err(error) => {
187 issues.push(issue_from_error(
188 SignatureValidationIssueKind::MissingStructure,
189 "missing_signature",
190 error,
191 ));
192 return Ok(SignatureValidationReport::from_issues(
193 None, None, None, issues,
194 ));
195 }
196 };
197 let parsed_signed_info = match required_child(document, signature, "SignedInfo")
198 .and_then(|signed_info| parse_signed_info(document, signed_info))
199 {
200 Ok(parsed) => Some(parsed),
201 Err(error) => {
202 issues.push(issue_from_error(
203 SignatureValidationIssueKind::MissingStructure,
204 "invalid_signed_info",
205 error,
206 ));
207 None
208 }
209 };
210
211 let xmldsig_valid =
212 match verify_enveloped(document, provider, self.xades_config.xmldsig_config()) {
213 Ok(report) => {
214 if !report.valid {
215 issues.push(SignatureValidationIssue::new(
216 SignatureValidationIssueKind::Cryptographic,
217 "xmldsig_invalid",
218 "XMLDSig verification failed",
219 ));
220 }
221 Some(report.valid)
222 }
223 Err(error) => {
224 issues.push(issue_from_error(
225 SignatureValidationIssueKind::MissingStructure,
226 "xmldsig_error",
227 error,
228 ));
229 None
230 }
231 };
232
233 if let Some(signed_info) = &parsed_signed_info {
234 self.validate_algorithms(signed_info, &mut issues);
235 self.validate_required_references(document, signature, signed_info, &mut issues)?;
236 }
237 let placement_valid =
238 self.validate_signature_placement(document, signature, &mut issues)?;
239 let xades_valid = self.validate_xades_level(document, provider, signature, &mut issues)?;
240 self.validate_external_material(document, provider, signature, &mut issues)?;
241
242 Ok(SignatureValidationReport::from_issues(
243 xmldsig_valid,
244 xades_valid,
245 placement_valid,
246 issues,
247 ))
248 }
249
250 fn validate_algorithms(
251 &self,
252 signed_info: &super::SignedInfo,
253 issues: &mut Vec<SignatureValidationIssue>,
254 ) {
255 if !self.allowed_signature_algorithms.is_empty()
256 && !self
257 .allowed_signature_algorithms
258 .contains(&signed_info.signature_algorithm)
259 {
260 issues.push(SignatureValidationIssue::new(
261 SignatureValidationIssueKind::Algorithm,
262 "signature_algorithm_not_allowed",
263 format!(
264 "signature algorithm `{}` is not allowed",
265 signed_info.signature_algorithm.uri()
266 ),
267 ));
268 }
269
270 if !self.allowed_digest_algorithms.is_empty() {
271 for reference in &signed_info.references {
272 if !self
273 .allowed_digest_algorithms
274 .contains(&reference.digest_algorithm)
275 {
276 issues.push(SignatureValidationIssue::new(
277 SignatureValidationIssueKind::Algorithm,
278 "digest_algorithm_not_allowed",
279 format!(
280 "digest algorithm `{}` is not allowed for reference `{}`",
281 reference.digest_algorithm.uri(),
282 reference.uri
283 ),
284 ));
285 }
286 }
287 }
288 }
289
290 fn validate_required_references(
291 &self,
292 document: &Document,
293 signature: NodeId,
294 signed_info: &super::SignedInfo,
295 issues: &mut Vec<SignatureValidationIssue>,
296 ) -> XmlResult<()> {
297 for required in &self.required_references {
298 if !reference_present(document, signature, signed_info, *required)? {
299 issues.push(SignatureValidationIssue::new(
300 SignatureValidationIssueKind::MissingStructure,
301 format!("missing_reference_{required:?}"),
302 format!("required reference `{required:?}` is missing"),
303 ));
304 }
305 }
306 Ok(())
307 }
308
309 fn validate_signature_placement(
310 &self,
311 document: &Document,
312 signature: NodeId,
313 issues: &mut Vec<SignatureValidationIssue>,
314 ) -> XmlResult<Option<bool>> {
315 let Some(expected) = &self.expected_signature_placement else {
316 return Ok(None);
317 };
318
319 let actual_parent = document.parent(signature)?;
320 let expected_parent = match expected {
321 SignaturePlacement::Root => document.root(),
322 SignaturePlacement::ParentNode(node) => Some(*node),
323 SignaturePlacement::Query { .. } => Some(resolve_signature_parent(
324 document,
325 &XmlDsigConfig::new().with_signature_placement(expected.clone()),
326 )?),
327 };
328 let valid = actual_parent == expected_parent;
329 if !valid {
330 issues.push(SignatureValidationIssue::new(
331 SignatureValidationIssueKind::Placement,
332 "signature_placement_mismatch",
333 "signature is not placed at the expected location",
334 ));
335 }
336 Ok(Some(valid))
337 }
338
339 fn validate_xades_level(
340 &self,
341 document: &Document,
342 provider: &impl SigningProvider,
343 signature: NodeId,
344 issues: &mut Vec<SignatureValidationIssue>,
345 ) -> XmlResult<Option<bool>> {
346 match &self.level {
347 SignatureValidationLevel::XmlDsig => Ok(None),
348 SignatureValidationLevel::XadesBes
349 | SignatureValidationLevel::XadesT
350 | SignatureValidationLevel::XadesLt
351 | SignatureValidationLevel::XadesLta => {
352 let config = self.xades_config.clone().with_profile(XadesProfile::Bes);
353 let valid = match verify_xades_bes_enveloped(document, provider, &config) {
354 Ok(report) => report.valid,
355 Err(error) => {
356 issues.push(issue_from_error(
357 SignatureValidationIssueKind::MissingStructure,
358 "xades_bes_error",
359 error,
360 ));
361 false
362 }
363 };
364 if !valid {
365 issues.push(SignatureValidationIssue::new(
366 SignatureValidationIssueKind::Cryptographic,
367 "xades_invalid",
368 "XAdES verification failed",
369 ));
370 }
371 self.validate_unsigned_level(document, signature, issues)?;
372 Ok(Some(valid))
373 }
374 SignatureValidationLevel::XadesEpes(policy) => {
375 let config = self
376 .xades_config
377 .clone()
378 .with_profile(XadesProfile::Epes(policy.clone()));
379 let valid = match verify_xades_epes_enveloped(document, provider, &config) {
380 Ok(report) => {
381 if report.signature_policy_valid == Some(false) {
382 issues.push(SignatureValidationIssue::new(
383 SignatureValidationIssueKind::Policy,
384 "signature_policy_mismatch",
385 "signature policy does not match the expected policy",
386 ));
387 }
388 report.valid
389 }
390 Err(error) => {
391 issues.push(issue_from_error(
392 SignatureValidationIssueKind::MissingStructure,
393 "xades_epes_error",
394 error,
395 ));
396 false
397 }
398 };
399 Ok(Some(valid))
400 }
401 SignatureValidationLevel::XadesBaselineB { policy } => {
402 let config = self
403 .xades_config
404 .clone()
405 .with_profile(XadesProfile::BaselineB {
406 policy: policy.clone(),
407 });
408 let valid = match verify_xades_baseline_b_enveloped(document, provider, &config) {
409 Ok(report) => {
410 if report.signature_policy_valid == Some(false) {
411 issues.push(SignatureValidationIssue::new(
412 SignatureValidationIssueKind::Policy,
413 "signature_policy_mismatch",
414 "signature policy does not match the expected policy",
415 ));
416 }
417 report.valid
418 }
419 Err(error) => {
420 issues.push(issue_from_error(
421 SignatureValidationIssueKind::MissingStructure,
422 "xades_baseline_b_error",
423 error,
424 ));
425 false
426 }
427 };
428 Ok(Some(valid))
429 }
430 }
431 }
432
433 fn validate_unsigned_level(
434 &self,
435 document: &Document,
436 signature: NodeId,
437 issues: &mut Vec<SignatureValidationIssue>,
438 ) -> XmlResult<()> {
439 match self.level {
440 SignatureValidationLevel::XadesT
441 | SignatureValidationLevel::XadesLt
442 | SignatureValidationLevel::XadesLta => {
443 if !xades_unsigned_child_present(document, signature, "SignatureTimeStamp")? {
444 issues.push(SignatureValidationIssue::new(
445 SignatureValidationIssueKind::MissingStructure,
446 "missing_signature_timestamp",
447 "required XAdES SignatureTimeStamp is missing",
448 ));
449 }
450 }
451 _ => {}
452 }
453
454 match self.level {
455 SignatureValidationLevel::XadesLt | SignatureValidationLevel::XadesLta => {
456 let validation_report = verify_xades_validation_data(
457 document,
458 &XadesValidationDataConfig::new()
459 .require_certificate_values(true)
460 .require_revocation_values(true),
461 )?;
462 if !validation_report.required_material_present() {
463 issues.push(SignatureValidationIssue::new(
464 SignatureValidationIssueKind::MissingExternalMaterial,
465 "missing_validation_data",
466 "required XAdES validation data is missing",
467 ));
468 }
469 }
470 _ => {}
471 }
472
473 if matches!(self.level, SignatureValidationLevel::XadesLta)
474 && !xades_unsigned_child_present(document, signature, "ArchiveTimeStamp")?
475 {
476 issues.push(SignatureValidationIssue::new(
477 SignatureValidationIssueKind::MissingStructure,
478 "missing_archive_timestamp",
479 "required XAdES ArchiveTimeStamp is missing",
480 ));
481 }
482
483 Ok(())
484 }
485
486 fn validate_external_material(
487 &self,
488 document: &Document,
489 provider: &impl SigningProvider,
490 signature: NodeId,
491 issues: &mut Vec<SignatureValidationIssue>,
492 ) -> XmlResult<()> {
493 if self.require_certificate_chain {
494 if provider.certificate_chain_details()?.len() < 2 {
495 issues.push(SignatureValidationIssue::new(
496 SignatureValidationIssueKind::MissingExternalMaterial,
497 "missing_certificate_chain_provider_material",
498 "provider did not expose a certificate chain",
499 ));
500 }
501 if signing_certificate_count(document, signature)? < 2 {
502 issues.push(SignatureValidationIssue::new(
503 SignatureValidationIssueKind::MissingStructure,
504 "missing_certificate_chain_signed_property",
505 "SignedProperties does not include the required certificate chain",
506 ));
507 }
508 }
509
510 if self.require_revocation_values {
511 let validation_report = verify_xades_validation_data(
512 document,
513 &XadesValidationDataConfig::new()
514 .require_certificate_values(false)
515 .require_revocation_values(true),
516 )?;
517 if !validation_report.required_material_present() {
518 issues.push(SignatureValidationIssue::new(
519 SignatureValidationIssueKind::MissingExternalMaterial,
520 "missing_revocation_values",
521 "required revocation values are missing",
522 ));
523 }
524 }
525
526 Ok(())
527 }
528}
529
530impl Default for SignatureValidationProfile {
531 fn default() -> Self {
532 Self {
533 level: SignatureValidationLevel::XmlDsig,
534 xades_config: XadesConfig::new(),
535 required_references: Vec::new(),
536 allowed_signature_algorithms: Vec::new(),
537 allowed_digest_algorithms: Vec::new(),
538 expected_signature_placement: None,
539 require_certificate_chain: false,
540 require_revocation_values: false,
541 }
542 }
543}
544
545fn issue_from_error(
546 kind: SignatureValidationIssueKind,
547 code: impl Into<String>,
548 error: XmlError,
549) -> SignatureValidationIssue {
550 SignatureValidationIssue::new(kind, code, error.to_string())
551}
552
553fn reference_present(
554 document: &Document,
555 signature: NodeId,
556 signed_info: &super::SignedInfo,
557 required: SignatureValidationReference,
558) -> XmlResult<bool> {
559 let key_info_uri = optional_child_id_uri(document, signature, "KeyInfo")?;
560 let signed_properties_uri = signed_properties_id_uri(document, signature)?;
561
562 Ok(match required {
563 SignatureValidationReference::WholeDocument => signed_info
564 .references
565 .iter()
566 .any(|reference| reference.uri == ""),
567 SignatureValidationReference::KeyInfo => key_info_uri.as_deref().is_some_and(|uri| {
568 signed_info
569 .references
570 .iter()
571 .any(|reference| reference.uri == uri)
572 }),
573 SignatureValidationReference::SignedProperties => {
574 signed_info.references.iter().any(|reference| {
575 reference.type_uri.as_deref() == Some(XMLDSIG_SIGNED_PROPERTIES_TYPE_URI)
576 || signed_properties_uri
577 .as_deref()
578 .is_some_and(|uri| reference.uri == uri)
579 })
580 }
581 SignatureValidationReference::Document => signed_info.references.iter().any(|reference| {
582 reference.type_uri.is_none()
583 && reference.uri.starts_with('#')
584 && key_info_uri.as_deref() != Some(reference.uri.as_str())
585 && signed_properties_uri.as_deref() != Some(reference.uri.as_str())
586 }),
587 })
588}
589
590fn optional_child_id_uri(
591 document: &Document,
592 parent: NodeId,
593 local: &str,
594) -> XmlResult<Option<String>> {
595 let Some(child) = element_children(document, parent)?
596 .into_iter()
597 .find(|child| element_local_name_matches(document, *child, local))
598 else {
599 return Ok(None);
600 };
601 Ok(optional_attribute(document, child, "Id")?.map(|id| format!("#{id}")))
602}
603
604fn signed_properties_id_uri(document: &Document, signature: NodeId) -> XmlResult<Option<String>> {
605 let Some(qualifying_properties) = xades_qualifying_properties(document, signature)? else {
606 return Ok(None);
607 };
608 let Some(signed_properties) =
609 optional_xades_child(document, qualifying_properties, "SignedProperties")?
610 else {
611 return Ok(None);
612 };
613 Ok(optional_attribute(document, signed_properties, "Id")?.map(|id| format!("#{id}")))
614}
615
616fn signing_certificate_count(document: &Document, signature: NodeId) -> XmlResult<usize> {
617 let Some(qualifying_properties) = xades_qualifying_properties(document, signature)? else {
618 return Ok(0);
619 };
620 let Some(signed_properties) =
621 optional_xades_child(document, qualifying_properties, "SignedProperties")?
622 else {
623 return Ok(0);
624 };
625 let Some(signed_signature_properties) =
626 optional_xades_child(document, signed_properties, "SignedSignatureProperties")?
627 else {
628 return Ok(0);
629 };
630 let signing_certificate = optional_xades_child(
631 document,
632 signed_signature_properties,
633 "SigningCertificateV2",
634 )?
635 .or(optional_xades_child(
636 document,
637 signed_signature_properties,
638 "SigningCertificate",
639 )?);
640 let Some(signing_certificate) = signing_certificate else {
641 return Ok(0);
642 };
643 Ok(element_children(document, signing_certificate)?
644 .into_iter()
645 .filter(|node| is_xades_element(document, *node, "Cert"))
646 .count())
647}
648
649fn xades_unsigned_child_present(
650 document: &Document,
651 signature: NodeId,
652 local: &str,
653) -> XmlResult<bool> {
654 let Some(qualifying_properties) = xades_qualifying_properties(document, signature)? else {
655 return Ok(false);
656 };
657 let Some(unsigned_properties) =
658 optional_xades_child(document, qualifying_properties, "UnsignedProperties")?
659 else {
660 return Ok(false);
661 };
662 let Some(unsigned_signature_properties) =
663 optional_xades_child(document, unsigned_properties, "UnsignedSignatureProperties")?
664 else {
665 return Ok(false);
666 };
667 Ok(optional_xades_child(document, unsigned_signature_properties, local)?.is_some())
668}
669
670fn xades_qualifying_properties(
671 document: &Document,
672 signature: NodeId,
673) -> XmlResult<Option<NodeId>> {
674 let object = match required_child(document, signature, "Object") {
675 Ok(object) => object,
676 Err(_) => return Ok(None),
677 };
678 Ok(element_children(document, object)?
679 .into_iter()
680 .find(|child| is_xades_element(document, *child, "QualifyingProperties")))
681}
682
683fn optional_xades_child(
684 document: &Document,
685 parent: NodeId,
686 local: &str,
687) -> XmlResult<Option<NodeId>> {
688 Ok(element_children(document, parent)?
689 .into_iter()
690 .find(|child| is_xades_element(document, *child, local)))
691}
692
693fn is_xades_element(document: &Document, node: NodeId, local: &str) -> bool {
694 matches!(
695 document.node(node).map(|node| node.kind()),
696 Ok(NodeKind::Element(element))
697 if element.name().namespace_uri().map(|uri| uri.as_str()) == Some(XADES_NAMESPACE_URI)
698 && element.name().local() == local
699 )
700}
701
702fn element_local_name_matches(document: &Document, node: NodeId, local: &str) -> bool {
703 matches!(
704 document.node(node).map(|node| node.kind()),
705 Ok(NodeKind::Element(element)) if element.name().local() == local
706 )
707}
708
709#[cfg(test)]
710mod tests {
711 use crate::parser::parse_str;
712 use crate::signature::{
713 add_signature_timestamp, add_xades_validation_data, sign_enveloped,
714 sign_xades_bes_enveloped, sign_xades_epes_enveloped, DeterministicSigningProvider,
715 DeterministicTimestampAuthority, SignaturePolicyId, SignaturePolicyQualifier,
716 StaticValidationDataProvider, XadesTimestampConfig, XmlDsigReferenceConfig,
717 };
718
719 use super::*;
720
721 fn provider() -> DeterministicSigningProvider {
722 DeterministicSigningProvider::new(b"test-cert".to_vec(), b"test-secret".to_vec())
723 }
724
725 fn provider_with_chain() -> DeterministicSigningProvider {
726 use crate::signature::CertificateDetails;
727
728 provider().with_certificate_chain_details(vec![
729 CertificateDetails::new(b"test-cert".to_vec()),
730 CertificateDetails::new(b"issuer-cert".to_vec()),
731 ])
732 }
733
734 fn unsigned_document() -> XmlResult<Document> {
735 parse_str(r#"<Root Id="doc-1"><Extension><Payload>value</Payload></Extension></Root>"#)
736 }
737
738 fn policy() -> XmlResult<SignaturePolicy> {
739 Ok(SignaturePolicy::new(
740 SignaturePolicyId::Uri("urn:example:policy:v1".to_owned()),
741 DigestAlgorithm::Sha256,
742 super::super::digest_bytes(DigestAlgorithm::Sha256, b"example policy")?,
743 )
744 .with_qualifier(SignaturePolicyQualifier::SpUri(
745 "https://example.test/policy.pdf".to_owned(),
746 )))
747 }
748
749 #[test]
750 fn signature_validation_profile_accepts_required_references_and_algorithms() -> XmlResult<()> {
751 let config = XmlDsigConfig::new()
752 .with_key_info_id("key-info-1")
753 .with_references(vec![
754 XmlDsigReferenceConfig::document_id(),
755 XmlDsigReferenceConfig::key_info(),
756 ]);
757 let signed = sign_enveloped(&unsigned_document()?, &provider(), &config)?;
758 let profile = SignatureValidationProfile::new()
759 .require_reference(SignatureValidationReference::Document)
760 .require_reference(SignatureValidationReference::KeyInfo)
761 .allow_signature_algorithm(SignatureAlgorithm::RsaSha256)
762 .allow_digest_algorithm(DigestAlgorithm::Sha256);
763
764 let report = profile.validate(&signed, &provider())?;
765
766 assert!(report.valid);
767 assert!(report.required_references_valid);
768 assert!(report.algorithms_valid);
769 assert!(report.issues.is_empty());
770 Ok(())
771 }
772
773 #[test]
774 fn signature_validation_profile_reports_missing_signed_properties_reference() -> XmlResult<()> {
775 let signed = sign_enveloped(&unsigned_document()?, &provider(), &XmlDsigConfig::new())?;
776 let profile = SignatureValidationProfile::new()
777 .require_reference(SignatureValidationReference::SignedProperties);
778
779 let report = profile.validate(&signed, &provider())?;
780
781 assert!(!report.valid);
782 assert!(!report.required_references_valid);
783 assert!(report
784 .issues
785 .iter()
786 .any(|issue| issue.kind == SignatureValidationIssueKind::MissingStructure));
787 Ok(())
788 }
789
790 #[test]
791 fn signature_validation_profile_validates_epes_policy_and_placement() -> XmlResult<()> {
792 let policy = policy()?;
793 let xades_config = XadesConfig::new()
794 .with_profile(XadesProfile::Epes(policy.clone()))
795 .with_xmldsig_config(
796 XmlDsigConfig::new()
797 .with_signature_placement(SignaturePlacement::query("/Root/Extension")),
798 );
799 let signed = sign_xades_epes_enveloped(&unsigned_document()?, &provider(), &xades_config)?;
800 let profile = SignatureValidationProfile::new()
801 .with_level(SignatureValidationLevel::XadesEpes(policy))
802 .with_xades_config(xades_config)
803 .require_reference(SignatureValidationReference::Document)
804 .require_reference(SignatureValidationReference::SignedProperties)
805 .with_expected_signature_placement(SignaturePlacement::query("/Root/Extension"));
806
807 let report = profile.validate(&signed, &provider())?;
808
809 assert!(report.valid);
810 assert_eq!(report.xades_valid, Some(true));
811 assert_eq!(report.placement_valid, Some(true));
812 Ok(())
813 }
814
815 #[test]
816 fn signature_validation_profile_reports_placement_mismatch() -> XmlResult<()> {
817 let signed =
818 sign_xades_bes_enveloped(&unsigned_document()?, &provider(), &XadesConfig::new())?;
819 let profile = SignatureValidationProfile::new()
820 .with_level(SignatureValidationLevel::XadesBes)
821 .with_expected_signature_placement(SignaturePlacement::query("/Root/Extension"));
822
823 let report = profile.validate(&signed, &provider())?;
824
825 assert!(!report.valid);
826 assert_eq!(report.placement_valid, Some(false));
827 assert!(report
828 .issues
829 .iter()
830 .any(|issue| issue.kind == SignatureValidationIssueKind::Placement));
831 Ok(())
832 }
833
834 #[test]
835 fn signature_validation_profile_reports_missing_lt_material() -> XmlResult<()> {
836 let signed =
837 sign_xades_bes_enveloped(&unsigned_document()?, &provider(), &XadesConfig::new())?;
838 let profile = SignatureValidationProfile::new()
839 .with_level(SignatureValidationLevel::XadesLt)
840 .require_certificate_chain(true)
841 .require_revocation_values(true);
842
843 let report = profile.validate(&signed, &provider())?;
844
845 assert!(!report.valid);
846 assert!(!report.external_material_valid);
847 assert!(report
848 .issues
849 .iter()
850 .any(|issue| issue.kind == SignatureValidationIssueKind::MissingExternalMaterial));
851 Ok(())
852 }
853
854 #[test]
855 fn signature_validation_profile_accepts_lt_material_when_present() -> XmlResult<()> {
856 let signed = sign_xades_bes_enveloped(
857 &unsigned_document()?,
858 &provider_with_chain(),
859 &XadesConfig::new().with_certificate_chain(true),
860 )?;
861 let timestamped = add_signature_timestamp(
862 &signed,
863 &DeterministicTimestampAuthority::new(b"timestamp-secret"),
864 &XadesTimestampConfig::new(),
865 )?;
866 let enriched = add_xades_validation_data(
867 ×tamped,
868 &StaticValidationDataProvider::new()
869 .with_certificate(b"issuer-cert".to_vec())
870 .with_ocsp(b"ocsp".to_vec()),
871 &XadesValidationDataConfig::new(),
872 )?;
873 let profile = SignatureValidationProfile::new()
874 .with_level(SignatureValidationLevel::XadesLt)
875 .require_certificate_chain(true)
876 .require_revocation_values(true);
877
878 let report = profile.validate(&enriched, &provider_with_chain())?;
879
880 assert!(report.valid);
881 assert!(report.external_material_valid);
882 Ok(())
883 }
884}