Skip to main content

oxidize_pdf/signatures/
types.rs

1//! Core types for digital signature handling
2
3use std::fmt;
4
5/// Represents a byte range in a PDF document for signature calculation
6///
7/// ByteRange is used to specify which parts of the PDF are covered by
8/// the signature. It typically consists of two ranges:
9/// - From document start to before the /Contents value
10/// - From after the /Contents value to document end
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ByteRange {
13    /// The ranges as (offset, length) pairs
14    ranges: Vec<(u64, u64)>,
15}
16
17impl ByteRange {
18    /// Creates a new ByteRange from a list of (offset, length) pairs
19    pub fn new(ranges: Vec<(u64, u64)>) -> Self {
20        Self { ranges }
21    }
22
23    /// Creates a ByteRange from a PDF array [offset1 len1 offset2 len2 ...]
24    pub fn from_array(values: &[i64]) -> Result<Self, String> {
25        if values.len() % 2 != 0 {
26            return Err("ByteRange array must have even number of elements".to_string());
27        }
28        if values.len() < 4 {
29            return Err("ByteRange array must have at least 4 elements".to_string());
30        }
31
32        let mut ranges = Vec::with_capacity(values.len() / 2);
33        for chunk in values.chunks(2) {
34            let offset = chunk[0];
35            let length = chunk[1];
36
37            if offset < 0 {
38                return Err(format!("ByteRange offset cannot be negative: {}", offset));
39            }
40            if length < 0 {
41                return Err(format!("ByteRange length cannot be negative: {}", length));
42            }
43
44            ranges.push((offset as u64, length as u64));
45        }
46
47        Ok(Self { ranges })
48    }
49
50    /// Returns the ranges as (offset, length) pairs
51    pub fn ranges(&self) -> &[(u64, u64)] {
52        &self.ranges
53    }
54
55    /// Returns the number of range pairs
56    pub fn len(&self) -> usize {
57        self.ranges.len()
58    }
59
60    /// Returns true if there are no ranges
61    pub fn is_empty(&self) -> bool {
62        self.ranges.is_empty()
63    }
64
65    /// Calculates the total number of bytes covered by all ranges
66    pub fn total_bytes(&self) -> u64 {
67        self.ranges.iter().map(|(_, len)| len).sum()
68    }
69
70    /// Validates that the ByteRange covers the expected document structure
71    ///
72    /// A valid signature ByteRange should:
73    /// - Have exactly 2 ranges (before and after /Contents)
74    /// - First range starts at 0
75    /// - Ranges don't overlap
76    pub fn validate(&self) -> Result<(), String> {
77        if self.ranges.len() != 2 {
78            return Err(format!(
79                "Expected 2 ranges for signature, got {}",
80                self.ranges.len()
81            ));
82        }
83
84        let (offset1, _len1) = self.ranges[0];
85        if offset1 != 0 {
86            return Err(format!(
87                "First range should start at offset 0, got {}",
88                offset1
89            ));
90        }
91
92        // Check ranges don't overlap
93        let (offset1, len1) = self.ranges[0];
94        let (offset2, _len2) = self.ranges[1];
95        if offset2 < offset1 + len1 {
96            return Err("ByteRange ranges overlap".to_string());
97        }
98
99        Ok(())
100    }
101}
102
103impl fmt::Display for ByteRange {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(f, "[")?;
106        for (i, (offset, length)) in self.ranges.iter().enumerate() {
107            if i > 0 {
108                write!(f, " ")?;
109            }
110            write!(f, "{} {}", offset, length)?;
111        }
112        write!(f, "]")
113    }
114}
115
116/// Represents a signature field in a PDF document
117#[derive(Debug, Clone)]
118pub struct SignatureField {
119    /// Field name (from /T entry)
120    pub name: Option<String>,
121
122    /// The byte range covered by the signature
123    pub byte_range: ByteRange,
124
125    /// The raw signature contents (PKCS#7/CMS data)
126    pub contents: Vec<u8>,
127
128    /// Signature filter (e.g., "Adobe.PPKLite")
129    pub filter: String,
130
131    /// Signature sub-filter (e.g., "adbe.pkcs7.detached", "ETSI.CAdES.detached")
132    pub sub_filter: Option<String>,
133
134    /// Signing reason (from /Reason entry)
135    pub reason: Option<String>,
136
137    /// Signing location (from /Location entry)
138    pub location: Option<String>,
139
140    /// Contact info (from /ContactInfo entry)
141    pub contact_info: Option<String>,
142
143    /// Signing time (from /M entry, PDF date format)
144    pub signing_time: Option<String>,
145}
146
147impl SignatureField {
148    /// Creates a new SignatureField with required fields only
149    pub fn new(filter: String, byte_range: ByteRange, contents: Vec<u8>) -> Self {
150        Self {
151            name: None,
152            byte_range,
153            contents,
154            filter,
155            sub_filter: None,
156            reason: None,
157            location: None,
158            contact_info: None,
159            signing_time: None,
160        }
161    }
162
163    /// Returns true if this is a PAdES signature
164    pub fn is_pades(&self) -> bool {
165        self.sub_filter
166            .as_ref()
167            .map(|sf| sf.contains("CAdES") || sf.contains("cades"))
168            .unwrap_or(false)
169    }
170
171    /// Returns true if this is a PKCS#7 detached signature
172    pub fn is_pkcs7_detached(&self) -> bool {
173        self.sub_filter
174            .as_ref()
175            .map(|sf| sf.contains("pkcs7.detached"))
176            .unwrap_or(false)
177    }
178
179    /// Returns the size of the signature contents in bytes
180    pub fn contents_size(&self) -> usize {
181        self.contents.len()
182    }
183}
184
185impl fmt::Display for SignatureField {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        write!(
188            f,
189            "SignatureField {{ name: {:?}, filter: {}, sub_filter: {:?}, contents: {} bytes }}",
190            self.name,
191            self.filter,
192            self.sub_filter,
193            self.contents.len()
194        )
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    // ByteRange tests
203
204    #[test]
205    fn test_byterange_from_array_valid() {
206        let values = vec![0, 1000, 2000, 500];
207        let br = ByteRange::from_array(&values).unwrap();
208        assert_eq!(br.len(), 2);
209        assert_eq!(br.ranges()[0], (0, 1000));
210        assert_eq!(br.ranges()[1], (2000, 500));
211    }
212
213    #[test]
214    fn test_byterange_from_array_odd_elements() {
215        let values = vec![0, 1000, 2000];
216        let result = ByteRange::from_array(&values);
217        assert!(result.is_err());
218        assert!(result.unwrap_err().contains("even"));
219    }
220
221    #[test]
222    fn test_byterange_from_array_too_few_elements() {
223        let values = vec![0, 1000];
224        let result = ByteRange::from_array(&values);
225        assert!(result.is_err());
226        assert!(result.unwrap_err().contains("at least 4"));
227    }
228
229    #[test]
230    fn test_byterange_from_array_negative_offset() {
231        let values = vec![-1, 1000, 2000, 500];
232        let result = ByteRange::from_array(&values);
233        assert!(result.is_err());
234        assert!(result.unwrap_err().contains("negative"));
235    }
236
237    #[test]
238    fn test_byterange_from_array_negative_length() {
239        let values = vec![0, -100, 2000, 500];
240        let result = ByteRange::from_array(&values);
241        assert!(result.is_err());
242        assert!(result.unwrap_err().contains("negative"));
243    }
244
245    #[test]
246    fn test_byterange_total_bytes() {
247        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
248        assert_eq!(br.total_bytes(), 1500);
249    }
250
251    #[test]
252    fn test_byterange_validate_valid() {
253        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
254        assert!(br.validate().is_ok());
255    }
256
257    #[test]
258    fn test_byterange_validate_wrong_range_count() {
259        let br = ByteRange::new(vec![(0, 1000)]);
260        let result = br.validate();
261        assert!(result.is_err());
262        assert!(result.unwrap_err().contains("2 ranges"));
263    }
264
265    #[test]
266    fn test_byterange_validate_first_not_zero() {
267        let br = ByteRange::new(vec![(100, 1000), (2000, 500)]);
268        let result = br.validate();
269        assert!(result.is_err());
270        assert!(result.unwrap_err().contains("offset 0"));
271    }
272
273    #[test]
274    fn test_byterange_validate_overlapping() {
275        let br = ByteRange::new(vec![(0, 1000), (500, 500)]);
276        let result = br.validate();
277        assert!(result.is_err());
278        assert!(result.unwrap_err().contains("overlap"));
279    }
280
281    #[test]
282    fn test_byterange_display() {
283        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
284        assert_eq!(br.to_string(), "[0 1000 2000 500]");
285    }
286
287    #[test]
288    fn test_byterange_is_empty() {
289        let empty = ByteRange::new(vec![]);
290        assert!(empty.is_empty());
291
292        let non_empty = ByteRange::new(vec![(0, 100)]);
293        assert!(!non_empty.is_empty());
294    }
295
296    // SignatureField tests
297
298    #[test]
299    fn test_signature_field_new() {
300        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
301        let contents = vec![0x30, 0x82]; // Start of DER sequence
302        let sig = SignatureField::new("Adobe.PPKLite".to_string(), br, contents);
303
304        assert_eq!(sig.filter, "Adobe.PPKLite");
305        assert!(sig.name.is_none());
306        assert!(sig.sub_filter.is_none());
307    }
308
309    #[test]
310    fn test_signature_field_is_pades() {
311        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
312        let mut sig = SignatureField::new("Adobe.PPKLite".to_string(), br, vec![]);
313
314        assert!(!sig.is_pades());
315
316        sig.sub_filter = Some("ETSI.CAdES.detached".to_string());
317        assert!(sig.is_pades());
318    }
319
320    #[test]
321    fn test_signature_field_is_pkcs7_detached() {
322        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
323        let mut sig = SignatureField::new("Adobe.PPKLite".to_string(), br, vec![]);
324
325        assert!(!sig.is_pkcs7_detached());
326
327        sig.sub_filter = Some("adbe.pkcs7.detached".to_string());
328        assert!(sig.is_pkcs7_detached());
329    }
330
331    #[test]
332    fn test_signature_field_contents_size() {
333        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
334        let contents = vec![0u8; 4096];
335        let sig = SignatureField::new("Adobe.PPKLite".to_string(), br, contents);
336
337        assert_eq!(sig.contents_size(), 4096);
338    }
339
340    #[test]
341    fn test_signature_field_display() {
342        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
343        let mut sig = SignatureField::new("Adobe.PPKLite".to_string(), br, vec![0u8; 100]);
344        sig.name = Some("Signature1".to_string());
345        sig.sub_filter = Some("adbe.pkcs7.detached".to_string());
346
347        let display = sig.to_string();
348        assert!(display.contains("Signature1"));
349        assert!(display.contains("Adobe.PPKLite"));
350        assert!(display.contains("100 bytes"));
351    }
352
353    #[test]
354    fn test_signature_field_clone() {
355        let br = ByteRange::new(vec![(0, 1000), (2000, 500)]);
356        let sig = SignatureField::new("Adobe.PPKLite".to_string(), br, vec![1, 2, 3]);
357        let cloned = sig.clone();
358
359        assert_eq!(sig.filter, cloned.filter);
360        assert_eq!(sig.contents, cloned.contents);
361    }
362}