use lopdf::{Dictionary, Object};
use crate::{Document, Result};
#[derive(Clone, Debug)]
pub struct SignatureInfo {
pub field_name: String,
pub signer_name: Option<String>,
pub signing_time: Option<String>,
pub is_valid: bool,
pub reason: Option<String>,
}
impl Document {
#[cfg(feature = "digital-signature")]
pub fn verify_signatures(&self, pdf_bytes: &[u8]) -> Result<Vec<SignatureInfo>> {
let mut signatures = Vec::new();
let root_ref = self.inner.trailer.get(b"Root")?.as_reference()?;
let catalog = self.inner.get_object(root_ref)?.as_dict()?;
let acroform_ref = match catalog.get(b"AcroForm") {
Ok(Object::Reference(id)) => id,
_ => return Ok(signatures), };
let acroform = match self.inner.get_object(*acroform_ref) {
Ok(obj) => match obj.as_dict() {
Ok(dict) => dict,
Err(_) => return Ok(signatures),
},
Err(_) => return Ok(signatures),
};
let fields_array = match acroform.get(b"Fields") {
Ok(Object::Array(arr)) => arr,
_ => return Ok(signatures),
};
for field_obj in fields_array {
if let Object::Reference(field_id) = field_obj
&& let Ok(field_obj) = self.inner.get_object(*field_id)
&& let Ok(field) = field_obj.as_dict()
&& let Ok(Object::Name(name)) = field.get(b"FT")
&& name == b"Sig"
{
if let Some(sig_info) = self.extract_signature_info(field, pdf_bytes) {
signatures.push(sig_info);
}
}
}
Ok(signatures)
}
#[cfg(not(feature = "digital-signature"))]
pub fn verify_signatures(&self, _pdf_bytes: &[u8]) -> Result<Vec<SignatureInfo>> {
Ok(Vec::new())
}
#[cfg(feature = "digital-signature")]
fn extract_signature_info(&self, field: &Dictionary, _pdf_bytes: &[u8]) -> Option<SignatureInfo> {
let field_name = match field.get(b"T") {
Ok(Object::String(bytes, _)) => String::from_utf8_lossy(bytes).to_string(),
_ => "unknown".to_string(),
};
let reason = match field.get(b"Reason") {
Ok(Object::String(bytes, _)) => Some(String::from_utf8_lossy(bytes).to_string()),
_ => None,
};
let signing_time = match field.get(b"M") {
Ok(Object::String(bytes, _)) => Some(String::from_utf8_lossy(bytes).to_string()),
_ => None,
};
let is_valid = false;
Some(SignatureInfo {
field_name,
signer_name: None, signing_time,
is_valid,
reason,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_signatures_returns_empty_for_unsigned_doc() {
let mut doc = Document::new((100.0, 100.0)).unwrap();
let pdf_bytes = doc.save_to_bytes().unwrap();
let signatures = doc.verify_signatures(&pdf_bytes).unwrap();
assert_eq!(signatures.len(), 0, "unsigned document should have no signatures");
}
}