use crate::core::{Document, NodeId, NodeKind, XmlError, XmlResult};
use super::xmldsig::{
element_children, find_signature, optional_attribute, parse_signed_info, required_child,
resolve_signature_parent,
};
use super::{
verify_enveloped, verify_xades_baseline_b_enveloped, verify_xades_bes_enveloped,
verify_xades_epes_enveloped, verify_xades_validation_data, DigestAlgorithm, SignatureAlgorithm,
SignaturePlacement, SignaturePolicy, SigningProvider, XadesConfig, XadesProfile,
XadesValidationDataConfig, XmlDsigConfig, XADES_NAMESPACE_URI,
XMLDSIG_SIGNED_PROPERTIES_TYPE_URI,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SignatureValidationLevel {
XmlDsig,
XadesBes,
XadesEpes(SignaturePolicy),
XadesBaselineB { policy: Option<SignaturePolicy> },
XadesT,
XadesLt,
XadesLta,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureValidationReference {
Document,
WholeDocument,
KeyInfo,
SignedProperties,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureValidationIssueKind {
Cryptographic,
MissingStructure,
MissingExternalMaterial,
Algorithm,
Policy,
Placement,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignatureValidationIssue {
pub kind: SignatureValidationIssueKind,
pub code: String,
pub message: String,
}
impl SignatureValidationIssue {
pub fn new(
kind: SignatureValidationIssueKind,
code: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
kind,
code: code.into(),
message: message.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignatureValidationReport {
pub valid: bool,
pub xmldsig_valid: Option<bool>,
pub xades_valid: Option<bool>,
pub required_references_valid: bool,
pub algorithms_valid: bool,
pub placement_valid: Option<bool>,
pub external_material_valid: bool,
pub issues: Vec<SignatureValidationIssue>,
}
impl SignatureValidationReport {
fn from_issues(
xmldsig_valid: Option<bool>,
xades_valid: Option<bool>,
placement_valid: Option<bool>,
issues: Vec<SignatureValidationIssue>,
) -> Self {
let required_references_valid = !issues
.iter()
.any(|issue| issue.code.starts_with("missing_reference"));
let algorithms_valid = !issues
.iter()
.any(|issue| issue.kind == SignatureValidationIssueKind::Algorithm);
let external_material_valid = !issues
.iter()
.any(|issue| issue.kind == SignatureValidationIssueKind::MissingExternalMaterial);
let valid = issues.is_empty()
&& xmldsig_valid.unwrap_or(false)
&& xades_valid.unwrap_or(true)
&& placement_valid.unwrap_or(true);
Self {
valid,
xmldsig_valid,
xades_valid,
required_references_valid,
algorithms_valid,
placement_valid,
external_material_valid,
issues,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignatureValidationProfile {
level: SignatureValidationLevel,
xades_config: XadesConfig,
required_references: Vec<SignatureValidationReference>,
allowed_signature_algorithms: Vec<SignatureAlgorithm>,
allowed_digest_algorithms: Vec<DigestAlgorithm>,
expected_signature_placement: Option<SignaturePlacement>,
require_certificate_chain: bool,
require_revocation_values: bool,
}
impl SignatureValidationProfile {
pub fn new() -> Self {
Self::default()
}
pub fn with_level(mut self, level: SignatureValidationLevel) -> Self {
self.level = level;
self
}
pub fn with_xades_config(mut self, config: XadesConfig) -> Self {
self.xades_config = config;
self
}
pub fn require_reference(mut self, reference: SignatureValidationReference) -> Self {
self.required_references.push(reference);
self
}
pub fn allow_signature_algorithm(mut self, algorithm: SignatureAlgorithm) -> Self {
self.allowed_signature_algorithms.push(algorithm);
self
}
pub fn allow_digest_algorithm(mut self, algorithm: DigestAlgorithm) -> Self {
self.allowed_digest_algorithms.push(algorithm);
self
}
pub fn with_expected_signature_placement(mut self, placement: SignaturePlacement) -> Self {
self.expected_signature_placement = Some(placement);
self
}
pub fn require_certificate_chain(mut self, required: bool) -> Self {
self.require_certificate_chain = required;
self
}
pub fn require_revocation_values(mut self, required: bool) -> Self {
self.require_revocation_values = required;
self
}
pub fn validate(
&self,
document: &Document,
provider: &impl SigningProvider,
) -> XmlResult<SignatureValidationReport> {
let mut issues = Vec::new();
let signature = match find_signature(document) {
Ok(signature) => signature,
Err(error) => {
issues.push(issue_from_error(
SignatureValidationIssueKind::MissingStructure,
"missing_signature",
error,
));
return Ok(SignatureValidationReport::from_issues(
None, None, None, issues,
));
}
};
let parsed_signed_info = match required_child(document, signature, "SignedInfo")
.and_then(|signed_info| parse_signed_info(document, signed_info))
{
Ok(parsed) => Some(parsed),
Err(error) => {
issues.push(issue_from_error(
SignatureValidationIssueKind::MissingStructure,
"invalid_signed_info",
error,
));
None
}
};
let xmldsig_valid =
match verify_enveloped(document, provider, self.xades_config.xmldsig_config()) {
Ok(report) => {
if !report.valid {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::Cryptographic,
"xmldsig_invalid",
"XMLDSig verification failed",
));
}
Some(report.valid)
}
Err(error) => {
issues.push(issue_from_error(
SignatureValidationIssueKind::MissingStructure,
"xmldsig_error",
error,
));
None
}
};
if let Some(signed_info) = &parsed_signed_info {
self.validate_algorithms(signed_info, &mut issues);
self.validate_required_references(document, signature, signed_info, &mut issues)?;
}
let placement_valid =
self.validate_signature_placement(document, signature, &mut issues)?;
let xades_valid = self.validate_xades_level(document, provider, signature, &mut issues)?;
self.validate_external_material(document, provider, signature, &mut issues)?;
Ok(SignatureValidationReport::from_issues(
xmldsig_valid,
xades_valid,
placement_valid,
issues,
))
}
fn validate_algorithms(
&self,
signed_info: &super::SignedInfo,
issues: &mut Vec<SignatureValidationIssue>,
) {
if !self.allowed_signature_algorithms.is_empty()
&& !self
.allowed_signature_algorithms
.contains(&signed_info.signature_algorithm)
{
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::Algorithm,
"signature_algorithm_not_allowed",
format!(
"signature algorithm `{}` is not allowed",
signed_info.signature_algorithm.uri()
),
));
}
if !self.allowed_digest_algorithms.is_empty() {
for reference in &signed_info.references {
if !self
.allowed_digest_algorithms
.contains(&reference.digest_algorithm)
{
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::Algorithm,
"digest_algorithm_not_allowed",
format!(
"digest algorithm `{}` is not allowed for reference `{}`",
reference.digest_algorithm.uri(),
reference.uri
),
));
}
}
}
}
fn validate_required_references(
&self,
document: &Document,
signature: NodeId,
signed_info: &super::SignedInfo,
issues: &mut Vec<SignatureValidationIssue>,
) -> XmlResult<()> {
for required in &self.required_references {
if !reference_present(document, signature, signed_info, *required)? {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::MissingStructure,
format!("missing_reference_{required:?}"),
format!("required reference `{required:?}` is missing"),
));
}
}
Ok(())
}
fn validate_signature_placement(
&self,
document: &Document,
signature: NodeId,
issues: &mut Vec<SignatureValidationIssue>,
) -> XmlResult<Option<bool>> {
let Some(expected) = &self.expected_signature_placement else {
return Ok(None);
};
let actual_parent = document.parent(signature)?;
let expected_parent = match expected {
SignaturePlacement::Root => document.root(),
SignaturePlacement::ParentNode(node) => Some(*node),
SignaturePlacement::Query { .. } => Some(resolve_signature_parent(
document,
&XmlDsigConfig::new().with_signature_placement(expected.clone()),
)?),
};
let valid = actual_parent == expected_parent;
if !valid {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::Placement,
"signature_placement_mismatch",
"signature is not placed at the expected location",
));
}
Ok(Some(valid))
}
fn validate_xades_level(
&self,
document: &Document,
provider: &impl SigningProvider,
signature: NodeId,
issues: &mut Vec<SignatureValidationIssue>,
) -> XmlResult<Option<bool>> {
match &self.level {
SignatureValidationLevel::XmlDsig => Ok(None),
SignatureValidationLevel::XadesBes
| SignatureValidationLevel::XadesT
| SignatureValidationLevel::XadesLt
| SignatureValidationLevel::XadesLta => {
let config = self.xades_config.clone().with_profile(XadesProfile::Bes);
let valid = match verify_xades_bes_enveloped(document, provider, &config) {
Ok(report) => report.valid,
Err(error) => {
issues.push(issue_from_error(
SignatureValidationIssueKind::MissingStructure,
"xades_bes_error",
error,
));
false
}
};
if !valid {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::Cryptographic,
"xades_invalid",
"XAdES verification failed",
));
}
self.validate_unsigned_level(document, signature, issues)?;
Ok(Some(valid))
}
SignatureValidationLevel::XadesEpes(policy) => {
let config = self
.xades_config
.clone()
.with_profile(XadesProfile::Epes(policy.clone()));
let valid = match verify_xades_epes_enveloped(document, provider, &config) {
Ok(report) => {
if report.signature_policy_valid == Some(false) {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::Policy,
"signature_policy_mismatch",
"signature policy does not match the expected policy",
));
}
report.valid
}
Err(error) => {
issues.push(issue_from_error(
SignatureValidationIssueKind::MissingStructure,
"xades_epes_error",
error,
));
false
}
};
Ok(Some(valid))
}
SignatureValidationLevel::XadesBaselineB { policy } => {
let config = self
.xades_config
.clone()
.with_profile(XadesProfile::BaselineB {
policy: policy.clone(),
});
let valid = match verify_xades_baseline_b_enveloped(document, provider, &config) {
Ok(report) => {
if report.signature_policy_valid == Some(false) {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::Policy,
"signature_policy_mismatch",
"signature policy does not match the expected policy",
));
}
report.valid
}
Err(error) => {
issues.push(issue_from_error(
SignatureValidationIssueKind::MissingStructure,
"xades_baseline_b_error",
error,
));
false
}
};
Ok(Some(valid))
}
}
}
fn validate_unsigned_level(
&self,
document: &Document,
signature: NodeId,
issues: &mut Vec<SignatureValidationIssue>,
) -> XmlResult<()> {
match self.level {
SignatureValidationLevel::XadesT
| SignatureValidationLevel::XadesLt
| SignatureValidationLevel::XadesLta => {
if !xades_unsigned_child_present(document, signature, "SignatureTimeStamp")? {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::MissingStructure,
"missing_signature_timestamp",
"required XAdES SignatureTimeStamp is missing",
));
}
}
_ => {}
}
match self.level {
SignatureValidationLevel::XadesLt | SignatureValidationLevel::XadesLta => {
let validation_report = verify_xades_validation_data(
document,
&XadesValidationDataConfig::new()
.require_certificate_values(true)
.require_revocation_values(true),
)?;
if !validation_report.required_material_present() {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::MissingExternalMaterial,
"missing_validation_data",
"required XAdES validation data is missing",
));
}
}
_ => {}
}
if matches!(self.level, SignatureValidationLevel::XadesLta)
&& !xades_unsigned_child_present(document, signature, "ArchiveTimeStamp")?
{
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::MissingStructure,
"missing_archive_timestamp",
"required XAdES ArchiveTimeStamp is missing",
));
}
Ok(())
}
fn validate_external_material(
&self,
document: &Document,
provider: &impl SigningProvider,
signature: NodeId,
issues: &mut Vec<SignatureValidationIssue>,
) -> XmlResult<()> {
if self.require_certificate_chain {
if provider.certificate_chain_details()?.len() < 2 {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::MissingExternalMaterial,
"missing_certificate_chain_provider_material",
"provider did not expose a certificate chain",
));
}
if signing_certificate_count(document, signature)? < 2 {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::MissingStructure,
"missing_certificate_chain_signed_property",
"SignedProperties does not include the required certificate chain",
));
}
}
if self.require_revocation_values {
let validation_report = verify_xades_validation_data(
document,
&XadesValidationDataConfig::new()
.require_certificate_values(false)
.require_revocation_values(true),
)?;
if !validation_report.required_material_present() {
issues.push(SignatureValidationIssue::new(
SignatureValidationIssueKind::MissingExternalMaterial,
"missing_revocation_values",
"required revocation values are missing",
));
}
}
Ok(())
}
}
impl Default for SignatureValidationProfile {
fn default() -> Self {
Self {
level: SignatureValidationLevel::XmlDsig,
xades_config: XadesConfig::new(),
required_references: Vec::new(),
allowed_signature_algorithms: Vec::new(),
allowed_digest_algorithms: Vec::new(),
expected_signature_placement: None,
require_certificate_chain: false,
require_revocation_values: false,
}
}
}
fn issue_from_error(
kind: SignatureValidationIssueKind,
code: impl Into<String>,
error: XmlError,
) -> SignatureValidationIssue {
SignatureValidationIssue::new(kind, code, error.to_string())
}
fn reference_present(
document: &Document,
signature: NodeId,
signed_info: &super::SignedInfo,
required: SignatureValidationReference,
) -> XmlResult<bool> {
let key_info_uri = optional_child_id_uri(document, signature, "KeyInfo")?;
let signed_properties_uri = signed_properties_id_uri(document, signature)?;
Ok(match required {
SignatureValidationReference::WholeDocument => signed_info
.references
.iter()
.any(|reference| reference.uri == ""),
SignatureValidationReference::KeyInfo => key_info_uri.as_deref().is_some_and(|uri| {
signed_info
.references
.iter()
.any(|reference| reference.uri == uri)
}),
SignatureValidationReference::SignedProperties => {
signed_info.references.iter().any(|reference| {
reference.type_uri.as_deref() == Some(XMLDSIG_SIGNED_PROPERTIES_TYPE_URI)
|| signed_properties_uri
.as_deref()
.is_some_and(|uri| reference.uri == uri)
})
}
SignatureValidationReference::Document => signed_info.references.iter().any(|reference| {
reference.type_uri.is_none()
&& reference.uri.starts_with('#')
&& key_info_uri.as_deref() != Some(reference.uri.as_str())
&& signed_properties_uri.as_deref() != Some(reference.uri.as_str())
}),
})
}
fn optional_child_id_uri(
document: &Document,
parent: NodeId,
local: &str,
) -> XmlResult<Option<String>> {
let Some(child) = element_children(document, parent)?
.into_iter()
.find(|child| element_local_name_matches(document, *child, local))
else {
return Ok(None);
};
Ok(optional_attribute(document, child, "Id")?.map(|id| format!("#{id}")))
}
fn signed_properties_id_uri(document: &Document, signature: NodeId) -> XmlResult<Option<String>> {
let Some(qualifying_properties) = xades_qualifying_properties(document, signature)? else {
return Ok(None);
};
let Some(signed_properties) =
optional_xades_child(document, qualifying_properties, "SignedProperties")?
else {
return Ok(None);
};
Ok(optional_attribute(document, signed_properties, "Id")?.map(|id| format!("#{id}")))
}
fn signing_certificate_count(document: &Document, signature: NodeId) -> XmlResult<usize> {
let Some(qualifying_properties) = xades_qualifying_properties(document, signature)? else {
return Ok(0);
};
let Some(signed_properties) =
optional_xades_child(document, qualifying_properties, "SignedProperties")?
else {
return Ok(0);
};
let Some(signed_signature_properties) =
optional_xades_child(document, signed_properties, "SignedSignatureProperties")?
else {
return Ok(0);
};
let signing_certificate = optional_xades_child(
document,
signed_signature_properties,
"SigningCertificateV2",
)?
.or(optional_xades_child(
document,
signed_signature_properties,
"SigningCertificate",
)?);
let Some(signing_certificate) = signing_certificate else {
return Ok(0);
};
Ok(element_children(document, signing_certificate)?
.into_iter()
.filter(|node| is_xades_element(document, *node, "Cert"))
.count())
}
fn xades_unsigned_child_present(
document: &Document,
signature: NodeId,
local: &str,
) -> XmlResult<bool> {
let Some(qualifying_properties) = xades_qualifying_properties(document, signature)? else {
return Ok(false);
};
let Some(unsigned_properties) =
optional_xades_child(document, qualifying_properties, "UnsignedProperties")?
else {
return Ok(false);
};
let Some(unsigned_signature_properties) =
optional_xades_child(document, unsigned_properties, "UnsignedSignatureProperties")?
else {
return Ok(false);
};
Ok(optional_xades_child(document, unsigned_signature_properties, local)?.is_some())
}
fn xades_qualifying_properties(
document: &Document,
signature: NodeId,
) -> XmlResult<Option<NodeId>> {
let object = match required_child(document, signature, "Object") {
Ok(object) => object,
Err(_) => return Ok(None),
};
Ok(element_children(document, object)?
.into_iter()
.find(|child| is_xades_element(document, *child, "QualifyingProperties")))
}
fn optional_xades_child(
document: &Document,
parent: NodeId,
local: &str,
) -> XmlResult<Option<NodeId>> {
Ok(element_children(document, parent)?
.into_iter()
.find(|child| is_xades_element(document, *child, local)))
}
fn is_xades_element(document: &Document, node: NodeId, local: &str) -> bool {
matches!(
document.node(node).map(|node| node.kind()),
Ok(NodeKind::Element(element))
if element.name().namespace_uri().map(|uri| uri.as_str()) == Some(XADES_NAMESPACE_URI)
&& element.name().local() == local
)
}
fn element_local_name_matches(document: &Document, node: NodeId, local: &str) -> bool {
matches!(
document.node(node).map(|node| node.kind()),
Ok(NodeKind::Element(element)) if element.name().local() == local
)
}
#[cfg(test)]
mod tests {
use crate::parser::parse_str;
use crate::signature::{
add_signature_timestamp, add_xades_validation_data, sign_enveloped,
sign_xades_bes_enveloped, sign_xades_epes_enveloped, DeterministicSigningProvider,
DeterministicTimestampAuthority, SignaturePolicyId, SignaturePolicyQualifier,
StaticValidationDataProvider, XadesTimestampConfig, XmlDsigReferenceConfig,
};
use super::*;
fn provider() -> DeterministicSigningProvider {
DeterministicSigningProvider::new(b"test-cert".to_vec(), b"test-secret".to_vec())
}
fn provider_with_chain() -> DeterministicSigningProvider {
use crate::signature::CertificateDetails;
provider().with_certificate_chain_details(vec![
CertificateDetails::new(b"test-cert".to_vec()),
CertificateDetails::new(b"issuer-cert".to_vec()),
])
}
fn unsigned_document() -> XmlResult<Document> {
parse_str(r#"<Root Id="doc-1"><Extension><Payload>value</Payload></Extension></Root>"#)
}
fn policy() -> XmlResult<SignaturePolicy> {
Ok(SignaturePolicy::new(
SignaturePolicyId::Uri("urn:example:policy:v1".to_owned()),
DigestAlgorithm::Sha256,
super::super::digest_bytes(DigestAlgorithm::Sha256, b"example policy")?,
)
.with_qualifier(SignaturePolicyQualifier::SpUri(
"https://example.test/policy.pdf".to_owned(),
)))
}
#[test]
fn signature_validation_profile_accepts_required_references_and_algorithms() -> XmlResult<()> {
let config = XmlDsigConfig::new()
.with_key_info_id("key-info-1")
.with_references(vec![
XmlDsigReferenceConfig::document_id(),
XmlDsigReferenceConfig::key_info(),
]);
let signed = sign_enveloped(&unsigned_document()?, &provider(), &config)?;
let profile = SignatureValidationProfile::new()
.require_reference(SignatureValidationReference::Document)
.require_reference(SignatureValidationReference::KeyInfo)
.allow_signature_algorithm(SignatureAlgorithm::RsaSha256)
.allow_digest_algorithm(DigestAlgorithm::Sha256);
let report = profile.validate(&signed, &provider())?;
assert!(report.valid);
assert!(report.required_references_valid);
assert!(report.algorithms_valid);
assert!(report.issues.is_empty());
Ok(())
}
#[test]
fn signature_validation_profile_reports_missing_signed_properties_reference() -> XmlResult<()> {
let signed = sign_enveloped(&unsigned_document()?, &provider(), &XmlDsigConfig::new())?;
let profile = SignatureValidationProfile::new()
.require_reference(SignatureValidationReference::SignedProperties);
let report = profile.validate(&signed, &provider())?;
assert!(!report.valid);
assert!(!report.required_references_valid);
assert!(report
.issues
.iter()
.any(|issue| issue.kind == SignatureValidationIssueKind::MissingStructure));
Ok(())
}
#[test]
fn signature_validation_profile_validates_epes_policy_and_placement() -> XmlResult<()> {
let policy = policy()?;
let xades_config = XadesConfig::new()
.with_profile(XadesProfile::Epes(policy.clone()))
.with_xmldsig_config(
XmlDsigConfig::new()
.with_signature_placement(SignaturePlacement::query("/Root/Extension")),
);
let signed = sign_xades_epes_enveloped(&unsigned_document()?, &provider(), &xades_config)?;
let profile = SignatureValidationProfile::new()
.with_level(SignatureValidationLevel::XadesEpes(policy))
.with_xades_config(xades_config)
.require_reference(SignatureValidationReference::Document)
.require_reference(SignatureValidationReference::SignedProperties)
.with_expected_signature_placement(SignaturePlacement::query("/Root/Extension"));
let report = profile.validate(&signed, &provider())?;
assert!(report.valid);
assert_eq!(report.xades_valid, Some(true));
assert_eq!(report.placement_valid, Some(true));
Ok(())
}
#[test]
fn signature_validation_profile_reports_placement_mismatch() -> XmlResult<()> {
let signed =
sign_xades_bes_enveloped(&unsigned_document()?, &provider(), &XadesConfig::new())?;
let profile = SignatureValidationProfile::new()
.with_level(SignatureValidationLevel::XadesBes)
.with_expected_signature_placement(SignaturePlacement::query("/Root/Extension"));
let report = profile.validate(&signed, &provider())?;
assert!(!report.valid);
assert_eq!(report.placement_valid, Some(false));
assert!(report
.issues
.iter()
.any(|issue| issue.kind == SignatureValidationIssueKind::Placement));
Ok(())
}
#[test]
fn signature_validation_profile_reports_missing_lt_material() -> XmlResult<()> {
let signed =
sign_xades_bes_enveloped(&unsigned_document()?, &provider(), &XadesConfig::new())?;
let profile = SignatureValidationProfile::new()
.with_level(SignatureValidationLevel::XadesLt)
.require_certificate_chain(true)
.require_revocation_values(true);
let report = profile.validate(&signed, &provider())?;
assert!(!report.valid);
assert!(!report.external_material_valid);
assert!(report
.issues
.iter()
.any(|issue| issue.kind == SignatureValidationIssueKind::MissingExternalMaterial));
Ok(())
}
#[test]
fn signature_validation_profile_accepts_lt_material_when_present() -> XmlResult<()> {
let signed = sign_xades_bes_enveloped(
&unsigned_document()?,
&provider_with_chain(),
&XadesConfig::new().with_certificate_chain(true),
)?;
let timestamped = add_signature_timestamp(
&signed,
&DeterministicTimestampAuthority::new(b"timestamp-secret"),
&XadesTimestampConfig::new(),
)?;
let enriched = add_xades_validation_data(
×tamped,
&StaticValidationDataProvider::new()
.with_certificate(b"issuer-cert".to_vec())
.with_ocsp(b"ocsp".to_vec()),
&XadesValidationDataConfig::new(),
)?;
let profile = SignatureValidationProfile::new()
.with_level(SignatureValidationLevel::XadesLt)
.require_certificate_chain(true)
.require_revocation_values(true);
let report = profile.validate(&enriched, &provider_with_chain())?;
assert!(report.valid);
assert!(report.external_material_valid);
Ok(())
}
}