rust_pdf_box/sign/
signer.rs1use crate::cos::object::{COSArray, COSDictionary, COSName, COSString};
2use crate::crypto::pkcs7::generate_pkcs7_detached;
3use crate::crypto::x509::SigningKey;
4use crate::error::{PdfError, PdfResult};
5use crate::pd::document::PDDocument;
6use crate::pd::info::format_pdf_date;
7use crate::pd::rectangle::PDRectangle;
8use crate::sign::visual::VisualSignature;
9use chrono::Utc;
10use sha2::{Digest, Sha256};
11
12const SIG_CONTENTS_BYTE_CAPACITY: usize = 4096; #[derive(Clone)]
16pub struct Signer {
17 pub key: SigningKey,
18 pub name: String,
19 pub reason: Option<String>,
20 pub location: Option<String>,
21 pub contact_info: Option<String>,
22 pub visual_signature: Option<VisualSignature>,
23}
24
25impl Signer {
26 pub fn new(key: SigningKey) -> Self {
27 Self {
28 key,
29 name: "Digital Signer".to_string(),
30 reason: None,
31 location: None,
32 contact_info: None,
33 visual_signature: None,
34 }
35 }
36
37 pub fn with_name(mut self, name: impl Into<String>) -> Self {
38 self.name = name.into();
39 self
40 }
41
42 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
43 self.reason = Some(reason.into());
44 self
45 }
46
47 pub fn with_location(mut self, location: impl Into<String>) -> Self {
48 self.location = Some(location.into());
49 self
50 }
51
52 pub fn with_contact_info(mut self, contact: impl Into<String>) -> Self {
53 self.contact_info = Some(contact.into());
54 self
55 }
56
57 pub fn with_visual_stamp(mut self, vis: VisualSignature) -> Self {
58 self.visual_signature = Some(vis);
59 self
60 }
61
62 pub fn sign_document(&self, doc: &mut PDDocument) -> PdfResult<Vec<u8>> {
64 let ap_stream_id = if let Some(ref vis) = self.visual_signature {
66 Some(vis.build_appearance_stream(&mut doc.cos_doc)?)
67 } else {
68 None
69 };
70
71 let sig_dict_id = doc.cos_doc.allocate_id();
73 let mut sig_dict = COSDictionary::new();
74 sig_dict.set("Type", COSName::new("Sig"));
75 sig_dict.set("Filter", COSName::new("Adobe.PPKLite"));
76 sig_dict.set("SubFilter", COSName::new("adbe.pkcs7.detached"));
77 sig_dict.set("Name", COSString::literal(self.name.clone().into_bytes()));
78
79 if let Some(ref reason) = self.reason {
80 sig_dict.set("Reason", COSString::literal(reason.clone().into_bytes()));
81 }
82 if let Some(ref location) = self.location {
83 sig_dict.set(
84 "Location",
85 COSString::literal(location.clone().into_bytes()),
86 );
87 }
88 if let Some(ref contact) = self.contact_info {
89 sig_dict.set(
90 "ContactInfo",
91 COSString::literal(contact.clone().into_bytes()),
92 );
93 }
94
95 let now = Utc::now();
96 sig_dict.set("M", COSString::literal(format_pdf_date(now).into_bytes()));
97
98 let mut byte_range_placeholder = COSArray::new();
100 byte_range_placeholder.push(1000000000i64);
101 byte_range_placeholder.push(1000000000i64);
102 byte_range_placeholder.push(1000000000i64);
103 byte_range_placeholder.push(1000000000i64);
104 sig_dict.set("ByteRange", byte_range_placeholder);
105
106 let placeholder_bytes = vec![0u8; SIG_CONTENTS_BYTE_CAPACITY];
108 sig_dict.set("Contents", COSString::hex(placeholder_bytes));
109
110 doc.cos_doc.set_object(sig_dict_id, sig_dict);
111
112 let sig_field_id = doc.cos_doc.allocate_id();
114 let mut sig_field = COSDictionary::new();
115 sig_field.set("Type", COSName::new("Annot"));
116 sig_field.set("Subtype", COSName::new("Widget"));
117 sig_field.set("FT", COSName::new("Sig"));
118 sig_field.set("T", COSString::literal("Signature1".as_bytes().to_vec()));
119 sig_field.set("V", sig_dict_id);
120 sig_field.set("F", 4); if let Some(ref vis) = self.visual_signature {
123 sig_field.set("Rect", vis.rect);
124 if let Some(ap_id) = ap_stream_id {
125 let mut ap_dict = COSDictionary::new();
126 ap_dict.set("N", ap_id);
127 sig_field.set("AP", ap_dict);
128 }
129
130 let page_idx = (vis.page_number.max(1) - 1).min(doc.page_count().saturating_sub(1));
132 let page = doc.get_page_mut(page_idx)?;
133 let mut annots_arr = page.dict.get_array("Annots").cloned().unwrap_or_default();
134 annots_arr.push(sig_field_id);
135 page.dict.set("Annots", annots_arr);
136 } else {
137 sig_field.set("Rect", PDRectangle::new(0.0, 0.0, 0.0, 0.0));
138 }
139
140 doc.cos_doc.set_object(sig_field_id, sig_field);
141
142 let mut catalog_dict = doc.cos_doc.get_catalog().cloned().unwrap_or_default();
144 let mut acro_form = catalog_dict
145 .get_dict("AcroForm")
146 .cloned()
147 .unwrap_or_default();
148 acro_form.set("SigFlags", 3); let mut fields = acro_form.get_array("Fields").cloned().unwrap_or_default();
151 fields.push(sig_field_id);
152 acro_form.set("Fields", fields);
153
154 let catalog_id = doc.catalog.catalog_id;
155 catalog_dict.set("AcroForm", acro_form);
156 doc.cos_doc.set_object(catalog_id, catalog_dict);
157
158 let mut pdf_buffer = Vec::new();
160 doc.save_to_writer(&mut pdf_buffer)?;
161
162 let contents_key = b"/Contents <";
164 let contents_start_pos = pdf_buffer
165 .windows(contents_key.len())
166 .position(|w| w == contents_key)
167 .ok_or_else(|| {
168 PdfError::Signature("Could not locate /Contents in serialized PDF".to_string())
169 })?
170 + b"/Contents ".len();
171
172 let hex_start = contents_start_pos + 1; let hex_end = hex_start + (SIG_CONTENTS_BYTE_CAPACITY * 2);
174
175 if hex_end >= pdf_buffer.len() || pdf_buffer[hex_end] != b'>' {
176 return Err(PdfError::Signature(
177 "Corrupted /Contents hex placeholder".to_string(),
178 ));
179 }
180
181 let offset1 = contents_start_pos;
182 let offset2 = hex_end + 1;
183 let len2 = pdf_buffer.len() - offset2;
184
185 let byte_range_key = b"/ByteRange [";
187 let byte_range_pos = pdf_buffer
188 .windows(byte_range_key.len())
189 .position(|w| w == byte_range_key)
190 .ok_or_else(|| {
191 PdfError::Signature("Could not locate /ByteRange in serialized PDF".to_string())
192 })?;
193
194 let byte_range_str = format!(
196 "/ByteRange [ 0 {:10} {:10} {:10} ]",
197 offset1, offset2, len2
198 );
199 let byte_range_bytes = byte_range_str.as_bytes();
200
201 pdf_buffer[byte_range_pos..byte_range_pos + byte_range_bytes.len()]
202 .copy_from_slice(byte_range_bytes);
203
204 let mut hasher = Sha256::new();
206 hasher.update(&pdf_buffer[0..offset1]);
207 hasher.update(&pdf_buffer[offset2..]);
208 let digest = hasher.finalize();
209
210 let p7_der = generate_pkcs7_detached(&self.key, &digest)?;
212 if p7_der.len() > SIG_CONTENTS_BYTE_CAPACITY {
213 return Err(PdfError::Signature(format!(
214 "Signature size {} exceeds reserved space {}",
215 p7_der.len(),
216 SIG_CONTENTS_BYTE_CAPACITY
217 )));
218 }
219
220 let hex_sig: String = p7_der.iter().map(|b| format!("{:02X}", b)).collect();
222 let hex_sig_bytes = hex_sig.as_bytes();
223
224 pdf_buffer[hex_start..hex_start + hex_sig_bytes.len()].copy_from_slice(hex_sig_bytes);
225
226 for b in &mut pdf_buffer[hex_start + hex_sig_bytes.len()..hex_end] {
228 *b = b'0';
229 }
230
231 Ok(pdf_buffer)
232 }
233}