mod certificate;
mod cms;
mod detection;
mod error;
mod types;
mod verification;
#[cfg(feature = "signatures")]
pub use certificate::validate_certificate_at_time;
pub use certificate::{validate_certificate, CertificateValidationResult, TrustStore};
pub use cms::{parse_pkcs7_signature, DigestAlgorithm, ParsedSignature, SignatureAlgorithm};
pub use detection::detect_signature_fields;
pub use error::{SignatureError, SignatureResult};
pub use types::{ByteRange, SignatureField};
pub use verification::{
compute_pdf_hash, has_incremental_update, hashes_match, verify_signature,
HashVerificationResult, SignatureVerificationResult,
};
#[derive(Debug, Clone)]
pub struct FullSignatureValidationResult {
pub field: SignatureField,
pub signer_name: Option<String>,
pub signing_time: Option<String>,
pub hash_valid: bool,
pub signature_valid: bool,
pub certificate_result: Option<CertificateValidationResult>,
pub has_modifications_after_signing: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl FullSignatureValidationResult {
pub fn is_valid(&self) -> bool {
self.hash_valid
&& self.signature_valid
&& self.errors.is_empty()
&& !self.has_modifications_after_signing
&& self
.certificate_result
.as_ref()
.map(|c| c.is_valid())
.unwrap_or(true)
}
pub fn signer_name(&self) -> &str {
self.signer_name.as_deref().unwrap_or("<unknown>")
}
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
|| self
.certificate_result
.as_ref()
.map(|c| c.has_warnings())
.unwrap_or(false)
}
pub fn validation_errors(&self) -> &[String] {
&self.errors
}
pub fn all_warnings(&self) -> Vec<String> {
let mut all = self.warnings.clone();
if let Some(cert) = &self.certificate_result {
all.extend(cert.warnings.clone());
}
if self.has_modifications_after_signing {
all.push("Document was modified after signing (incremental update)".to_string());
}
all
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn test_full_signature_validation_result_is_valid_all_pass() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100), (200, 100)]),
vec![1, 2, 3],
),
signer_name: Some("Test User".to_string()),
signing_time: Some("2024-01-01T12:00:00Z".to_string()),
hash_valid: true,
signature_valid: true,
certificate_result: Some(CertificateValidationResult {
subject: "CN=Test User".to_string(),
issuer: "CN=Test CA".to_string(),
valid_from: "2024-01-01".to_string(),
valid_to: "2025-01-01".to_string(),
is_time_valid: true,
is_trusted: true,
is_signature_capable: true,
warnings: vec![],
}),
has_modifications_after_signing: false,
errors: vec![],
warnings: vec![],
};
assert!(result.is_valid());
}
#[test]
fn test_full_signature_validation_result_invalid_hash() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100), (200, 100)]),
vec![1, 2, 3],
),
signer_name: None,
signing_time: None,
hash_valid: false,
signature_valid: true,
certificate_result: None,
has_modifications_after_signing: false,
errors: vec!["Hash mismatch".to_string()],
warnings: vec![],
};
assert!(!result.is_valid());
}
#[test]
fn test_full_signature_validation_result_invalid_signature() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100), (200, 100)]),
vec![1, 2, 3],
),
signer_name: None,
signing_time: None,
hash_valid: true,
signature_valid: false,
certificate_result: None,
has_modifications_after_signing: false,
errors: vec!["Signature verification failed".to_string()],
warnings: vec![],
};
assert!(!result.is_valid());
}
#[test]
fn test_full_signature_validation_result_modified_after_signing() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100), (200, 100)]),
vec![1, 2, 3],
),
signer_name: Some("Test".to_string()),
signing_time: None,
hash_valid: true,
signature_valid: true,
certificate_result: None,
has_modifications_after_signing: true,
errors: vec![],
warnings: vec![],
};
assert!(!result.is_valid());
}
#[test]
fn test_full_signature_validation_result_signer_name() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100)]),
vec![],
),
signer_name: Some("John Doe".to_string()),
signing_time: None,
hash_valid: true,
signature_valid: true,
certificate_result: None,
has_modifications_after_signing: false,
errors: vec![],
warnings: vec![],
};
assert_eq!(result.signer_name(), "John Doe");
}
#[test]
fn test_full_signature_validation_result_signer_name_unknown() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100)]),
vec![],
),
signer_name: None,
signing_time: None,
hash_valid: true,
signature_valid: true,
certificate_result: None,
has_modifications_after_signing: false,
errors: vec![],
warnings: vec![],
};
assert_eq!(result.signer_name(), "<unknown>");
}
#[test]
fn test_full_signature_validation_result_has_warnings() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100)]),
vec![],
),
signer_name: None,
signing_time: None,
hash_valid: true,
signature_valid: true,
certificate_result: None,
has_modifications_after_signing: false,
errors: vec![],
warnings: vec!["Test warning".to_string()],
};
assert!(result.has_warnings());
}
#[test]
fn test_full_signature_validation_result_all_warnings() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100)]),
vec![],
),
signer_name: None,
signing_time: None,
hash_valid: true,
signature_valid: true,
certificate_result: Some(CertificateValidationResult {
subject: "CN=Test".to_string(),
issuer: "CN=CA".to_string(),
valid_from: "2024-01-01".to_string(),
valid_to: "2025-01-01".to_string(),
is_time_valid: true,
is_trusted: true,
is_signature_capable: true,
warnings: vec!["Self-signed certificate".to_string()],
}),
has_modifications_after_signing: true,
errors: vec![],
warnings: vec!["Generic warning".to_string()],
};
let all = result.all_warnings();
assert_eq!(all.len(), 3);
assert!(all.contains(&"Generic warning".to_string()));
assert!(all.contains(&"Self-signed certificate".to_string()));
assert!(all.iter().any(|w| w.contains("modified after signing")));
}
#[test]
fn test_full_signature_validation_result_invalid_certificate() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100)]),
vec![],
),
signer_name: Some("Test".to_string()),
signing_time: None,
hash_valid: true,
signature_valid: true,
certificate_result: Some(CertificateValidationResult {
subject: "CN=Test".to_string(),
issuer: "CN=CA".to_string(),
valid_from: "2024-01-01".to_string(),
valid_to: "2025-01-01".to_string(),
is_time_valid: false, is_trusted: true,
is_signature_capable: true,
warnings: vec![],
}),
has_modifications_after_signing: false,
errors: vec![],
warnings: vec![],
};
assert!(!result.is_valid()); }
#[test]
fn test_full_signature_validation_result_clone() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100)]),
vec![],
),
signer_name: Some("Test".to_string()),
signing_time: None,
hash_valid: true,
signature_valid: true,
certificate_result: None,
has_modifications_after_signing: false,
errors: vec![],
warnings: vec![],
};
let cloned = result.clone();
assert_eq!(result.signer_name, cloned.signer_name);
assert_eq!(result.hash_valid, cloned.hash_valid);
}
#[test]
fn test_full_signature_validation_result_debug() {
let result = FullSignatureValidationResult {
field: SignatureField::new(
"Adobe.PPKLite".to_string(),
ByteRange::new(vec![(0, 100)]),
vec![],
),
signer_name: Some("Test".to_string()),
signing_time: None,
hash_valid: true,
signature_valid: true,
certificate_result: None,
has_modifications_after_signing: false,
errors: vec![],
warnings: vec![],
};
let debug = format!("{:?}", result);
assert!(debug.contains("hash_valid"));
assert!(debug.contains("signature_valid"));
}
}