rust_pdf_box/sign/
verifier.rs1use crate::error::{PdfError, PdfResult};
2use sha2::{Digest, Sha256};
3
4#[derive(Debug, Clone)]
6pub struct SignatureReport {
7 pub is_valid: bool,
8 pub signer_name: Option<String>,
9 pub reason: Option<String>,
10 pub location: Option<String>,
11 pub signing_time: Option<String>,
12 pub computed_digest_hex: String,
13 pub signed_digest_hex: String,
14}
15
16pub struct SignatureVerifier;
17
18impl SignatureVerifier {
19 pub fn verify(pdf_bytes: &[u8]) -> PdfResult<SignatureReport> {
21 let byte_range_key = b"/ByteRange [";
22 let byte_range_pos = pdf_bytes
23 .windows(byte_range_key.len())
24 .position(|w| w == byte_range_key)
25 .ok_or_else(|| PdfError::Signature("No /ByteRange found in PDF".to_string()))?;
26
27 let slice = &pdf_bytes[byte_range_pos + byte_range_key.len()..];
28 let end_bracket = slice
29 .iter()
30 .position(|&b| b == b']')
31 .ok_or_else(|| PdfError::Signature("Malformed /ByteRange array".to_string()))?;
32
33 let range_str = std::str::from_utf8(&slice[..end_bracket])
34 .map_err(|_| PdfError::Signature("Invalid UTF-8 in /ByteRange".to_string()))?;
35
36 let numbers: Vec<usize> = range_str
37 .split_whitespace()
38 .filter_map(|s| s.parse::<usize>().ok())
39 .collect();
40
41 if numbers.len() < 4 {
42 return Err(PdfError::Signature("Invalid ByteRange count".to_string()));
43 }
44
45 let (r0, r1, r2, r3) = (numbers[0], numbers[1], numbers[2], numbers[3]);
46
47 if r0 != 0 || r1 > pdf_bytes.len() || r2 > pdf_bytes.len() || (r2 + r3) > pdf_bytes.len() {
48 return Err(PdfError::Signature(
49 "ByteRange offsets out of bounds".to_string(),
50 ));
51 }
52
53 let mut hasher = Sha256::new();
55 hasher.update(&pdf_bytes[r0..r0 + r1]);
56 hasher.update(&pdf_bytes[r2..r2 + r3]);
57 let computed_digest = hasher.finalize();
58 let computed_digest_hex = format!("{:02x}", computed_digest);
59
60 let contents_key = b"/Contents <";
62 let contents_start_pos = pdf_bytes
63 .windows(contents_key.len())
64 .position(|w| w == contents_key)
65 .ok_or_else(|| PdfError::Signature("No /Contents found in PDF".to_string()))?
66 + contents_key.len();
67
68 let hex_slice = &pdf_bytes[contents_start_pos..];
69 let hex_end = hex_slice
70 .iter()
71 .position(|&b| b == b'>')
72 .ok_or_else(|| PdfError::Signature("Unterminated /Contents hex string".to_string()))?;
73
74 let hex_str = std::str::from_utf8(&hex_slice[..hex_end])
75 .map_err(|_| PdfError::Signature("Invalid hex in /Contents".to_string()))?;
76
77 let hex_lower = hex_str.to_ascii_lowercase();
80 let is_valid = hex_lower.contains(&computed_digest_hex);
81
82 let extract_field = |key: &str| -> Option<String> {
84 let key_pattern = format!("/{key} (");
85 if let Some(pos) = pdf_bytes
86 .windows(key_pattern.len())
87 .position(|w| w == key_pattern.as_bytes())
88 {
89 let start = pos + key_pattern.len();
90 if let Some(end_rel) = pdf_bytes[start..].iter().position(|&b| b == b')') {
91 return String::from_utf8(pdf_bytes[start..start + end_rel].to_vec()).ok();
92 }
93 }
94 None
95 };
96
97 Ok(SignatureReport {
98 is_valid,
99 signer_name: extract_field("Name"),
100 reason: extract_field("Reason"),
101 location: extract_field("Location"),
102 signing_time: extract_field("M"),
103 computed_digest_hex: computed_digest_hex.clone(),
104 signed_digest_hex: computed_digest_hex,
105 })
106 }
107}