use crate::core::{Document, ErrorKind, XmlError, XmlResult};
use super::{
add_signature_timestamp, add_xades_archive_timestamp, add_xades_validation_data,
sign_xades_baseline_b_enveloped, sign_xades_bes_enveloped, sign_xades_epes_enveloped,
verify_signature_timestamp, verify_xades_archive_timestamps, verify_xades_baseline_b_enveloped,
verify_xades_bes_enveloped, verify_xades_epes_enveloped, verify_xades_validation_data,
SignatureValidationProfile, SignatureValidationReport, SigningProvider,
TimestampAuthorityClient, TimestampValidationReport, XadesArchiveConfig, XadesArchiveReport,
XadesConfig, XadesProfile, XadesTimestampConfig, XadesValidationDataConfig,
XadesValidationDataProvider, XadesValidationDataReport, XadesVerificationReport,
};
pub struct XadesSigningOptions<'a> {
timestamp_authority: Option<&'a dyn TimestampAuthorityClient>,
timestamp_config: Option<XadesTimestampConfig>,
validation_data_provider: Option<&'a dyn XadesValidationDataProvider>,
validation_data_config: Option<XadesValidationDataConfig>,
archive_timestamp_authority: Option<&'a dyn TimestampAuthorityClient>,
archive_config: Option<XadesArchiveConfig>,
initial_validation_profile: Option<SignatureValidationProfile>,
final_validation_profile: Option<SignatureValidationProfile>,
}
impl<'a> XadesSigningOptions<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn with_timestamp<C>(mut self, client: &'a C, config: XadesTimestampConfig) -> Self
where
C: TimestampAuthorityClient + 'a,
{
self.timestamp_authority = Some(client);
self.timestamp_config = Some(config);
self
}
pub fn with_validation_data<P>(
mut self,
provider: &'a P,
config: XadesValidationDataConfig,
) -> Self
where
P: XadesValidationDataProvider + 'a,
{
self.validation_data_provider = Some(provider);
self.validation_data_config = Some(config);
self
}
pub fn with_archive_timestamp<C>(mut self, client: &'a C, config: XadesArchiveConfig) -> Self
where
C: TimestampAuthorityClient + 'a,
{
self.archive_timestamp_authority = Some(client);
self.archive_config = Some(config);
self
}
pub fn with_initial_validation_profile(mut self, profile: SignatureValidationProfile) -> Self {
self.initial_validation_profile = Some(profile);
self
}
pub fn with_final_validation_profile(mut self, profile: SignatureValidationProfile) -> Self {
self.final_validation_profile = Some(profile);
self
}
}
impl Default for XadesSigningOptions<'_> {
fn default() -> Self {
Self {
timestamp_authority: None,
timestamp_config: None,
validation_data_provider: None,
validation_data_config: None,
archive_timestamp_authority: None,
archive_config: None,
initial_validation_profile: None,
final_validation_profile: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XadesSigningReport {
pub initial_xades: XadesVerificationReport,
pub initial_validation_profile: Option<SignatureValidationReport>,
pub timestamp: Option<TimestampValidationReport>,
pub validation_data: Option<XadesValidationDataReport>,
pub archive: Option<XadesArchiveReport>,
pub final_xades: XadesVerificationReport,
pub final_validation_profile: Option<SignatureValidationReport>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XadesSignedDocument {
document: Document,
report: XadesSigningReport,
}
impl XadesSignedDocument {
pub fn new(document: Document, report: XadesSigningReport) -> Self {
Self { document, report }
}
pub fn document(&self) -> &Document {
&self.document
}
pub fn report(&self) -> &XadesSigningReport {
&self.report
}
pub fn into_document(self) -> Document {
self.document
}
pub fn into_parts(self) -> (Document, XadesSigningReport) {
(self.document, self.report)
}
}
#[derive(Debug, Clone)]
pub struct XadesSigner<P> {
provider: P,
config: XadesConfig,
}
impl<P> XadesSigner<P>
where
P: SigningProvider,
{
pub fn new(provider: P) -> Self {
Self {
provider,
config: XadesConfig::new(),
}
}
pub fn with_config(mut self, config: XadesConfig) -> Self {
self.config = config;
self
}
pub fn sign_document(&self, document: &Document) -> XmlResult<Document> {
match self.config.profile() {
XadesProfile::Bes => sign_xades_bes_enveloped(document, &self.provider, &self.config),
XadesProfile::Epes(_) => {
sign_xades_epes_enveloped(document, &self.provider, &self.config)
}
XadesProfile::BaselineB { .. } => {
sign_xades_baseline_b_enveloped(document, &self.provider, &self.config)
}
}
}
pub fn sign_document_with_options(
&self,
document: &Document,
options: &XadesSigningOptions<'_>,
) -> XmlResult<XadesSignedDocument> {
let mut signed = self.sign_document(document)?;
let initial_xades = self.verify_xades(&signed)?;
let initial_validation_profile = options
.initial_validation_profile
.as_ref()
.map(|profile| profile.validate(&signed, &self.provider))
.transpose()?;
let timestamp = if let Some(config) = &options.timestamp_config {
let client = options.timestamp_authority.ok_or_else(|| {
XmlError::new(
ErrorKind::Signature,
"XAdES timestamp config requires a timestamp authority",
)
})?;
signed = add_signature_timestamp(&signed, client, config)?;
Some(verify_signature_timestamp(&signed, client, config)?)
} else {
None
};
let validation_data = if let Some(config) = &options.validation_data_config {
let provider = options.validation_data_provider.ok_or_else(|| {
XmlError::new(
ErrorKind::Signature,
"XAdES validation data config requires a validation data provider",
)
})?;
signed = add_xades_validation_data(&signed, provider, config)?;
Some(verify_xades_validation_data(&signed, config)?)
} else {
None
};
let archive = if let Some(config) = &options.archive_config {
let client = options.archive_timestamp_authority.ok_or_else(|| {
XmlError::new(
ErrorKind::Signature,
"XAdES archive config requires a timestamp authority",
)
})?;
signed = add_xades_archive_timestamp(&signed, client, config)?;
Some(verify_xades_archive_timestamps(&signed, client, config)?)
} else {
None
};
let final_xades = self.verify_xades(&signed)?;
let final_validation_profile = options
.final_validation_profile
.as_ref()
.map(|profile| profile.validate(&signed, &self.provider))
.transpose()?;
Ok(XadesSignedDocument::new(
signed,
XadesSigningReport {
initial_xades,
initial_validation_profile,
timestamp,
validation_data,
archive,
final_xades,
final_validation_profile,
},
))
}
pub fn config(&self) -> &XadesConfig {
&self.config
}
pub fn provider(&self) -> &P {
&self.provider
}
fn verify_xades(&self, document: &Document) -> XmlResult<XadesVerificationReport> {
match self.config.profile() {
XadesProfile::Bes => verify_xades_bes_enveloped(document, &self.provider, &self.config),
XadesProfile::Epes(_) => {
verify_xades_epes_enveloped(document, &self.provider, &self.config)
}
XadesProfile::BaselineB { .. } => {
verify_xades_baseline_b_enveloped(document, &self.provider, &self.config)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::parser::parse_str;
use crate::signature::{
DeterministicSigningProvider, DeterministicTimestampAuthority, SignatureValidationLevel,
SignatureValidationProfile, StaticValidationDataProvider, XadesArchiveConfig,
XadesTimestampConfig, XadesValidationDataConfig,
};
use crate::writer::to_string_compact;
use super::*;
fn document() -> XmlResult<Document> {
parse_str(r#"<Root Id="doc-1"><Item>value</Item></Root>"#)
}
fn provider() -> DeterministicSigningProvider {
DeterministicSigningProvider::new(b"cert".to_vec(), b"secret".to_vec())
}
#[test]
fn signer_options_add_long_term_unsigned_properties() -> XmlResult<()> {
let timestamp_authority = DeterministicTimestampAuthority::new(b"tsa");
let validation_data = StaticValidationDataProvider::new()
.with_certificate(b"cert".to_vec())
.with_ocsp(b"ocsp".to_vec());
let final_profile = SignatureValidationProfile::new()
.with_level(SignatureValidationLevel::XadesLta)
.with_xades_config(XadesConfig::new());
let options = XadesSigningOptions::new()
.with_timestamp(×tamp_authority, XadesTimestampConfig::new())
.with_validation_data(&validation_data, XadesValidationDataConfig::new())
.with_archive_timestamp(×tamp_authority, XadesArchiveConfig::new())
.with_final_validation_profile(final_profile);
let signed =
XadesSigner::new(provider()).sign_document_with_options(&document()?, &options)?;
let xml = to_string_compact(signed.document())?;
let report = signed.report();
assert!(report.initial_xades.valid);
assert!(report.final_xades.valid);
assert!(report
.timestamp
.as_ref()
.is_some_and(|report| report.token_valid));
assert!(report
.validation_data
.as_ref()
.is_some_and(|report| report.required_material_present()));
assert!(report
.archive
.as_ref()
.is_some_and(|report| report.preservation_ready));
assert!(report
.final_validation_profile
.as_ref()
.is_some_and(|report| report.valid));
assert!(xml.contains("<xades:SignatureTimeStamp>"));
assert!(xml.contains("<xades:CertificateValues>"));
assert!(xml.contains("<xades:RevocationValues>"));
assert!(xml.contains("<xades:ArchiveTimeStamp>"));
Ok(())
}
#[test]
fn signer_options_can_validate_initial_signature_before_unsigned_steps() -> XmlResult<()> {
let initial_profile = SignatureValidationProfile::new()
.with_level(SignatureValidationLevel::XadesBes)
.with_xades_config(XadesConfig::new());
let options = XadesSigningOptions::new().with_initial_validation_profile(initial_profile);
let signed =
XadesSigner::new(provider()).sign_document_with_options(&document()?, &options)?;
let report = signed.report();
assert!(report.initial_xades.valid);
assert!(report.final_xades.valid);
assert!(report.timestamp.is_none());
assert!(report.validation_data.is_none());
assert!(report.archive.is_none());
assert!(report
.initial_validation_profile
.as_ref()
.is_some_and(|report| report.valid));
Ok(())
}
}