Skip to main content

oxidize_pdf/signatures/
error.rs

1//! Error types for digital signature operations
2
3use std::fmt;
4
5/// Result type for signature operations
6pub type SignatureResult<T> = Result<T, SignatureError>;
7
8/// Errors that can occur during signature operations
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum SignatureError {
11    /// Missing required field in signature dictionary
12    MissingField {
13        /// Name of the missing field
14        field: String,
15    },
16
17    /// Invalid ByteRange format
18    InvalidByteRange {
19        /// Description of what's wrong
20        details: String,
21    },
22
23    /// Invalid signature dictionary structure
24    InvalidSignatureDict {
25        /// Description of the issue
26        details: String,
27    },
28
29    /// Signature contents extraction failed
30    ContentsExtractionFailed {
31        /// Description of the failure
32        details: String,
33    },
34
35    /// AcroForm not found in document
36    AcroFormNotFound,
37
38    /// No signature fields in document
39    NoSignatureFields,
40
41    /// PDF parsing error during signature extraction
42    ParseError {
43        /// The underlying error message
44        message: String,
45    },
46
47    /// CMS/PKCS#7 structure parsing failed
48    CmsParsingFailed {
49        /// Description of the parsing failure
50        details: String,
51    },
52
53    /// Unsupported cryptographic algorithm
54    UnsupportedAlgorithm {
55        /// The algorithm that is not supported
56        algorithm: String,
57    },
58
59    /// ByteRange exceeds document size
60    ByteRangeExceedsDocument {
61        /// The byte range that's out of bounds
62        offset: u64,
63        /// The requested length
64        length: u64,
65        /// The document size
66        document_size: u64,
67    },
68
69    /// Hash verification failed
70    HashVerificationFailed {
71        /// Description of the failure
72        details: String,
73    },
74
75    /// Signature verification failed
76    SignatureVerificationFailed {
77        /// Description of the failure
78        details: String,
79    },
80
81    /// Certificate extraction failed
82    CertificateExtractionFailed {
83        /// Description of the failure
84        details: String,
85    },
86
87    /// Certificate validation failed
88    CertificateValidationFailed {
89        /// Description of the validation failure
90        details: String,
91    },
92}
93
94impl fmt::Display for SignatureError {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        match self {
97            Self::MissingField { field } => {
98                write!(f, "Missing required signature field: {}", field)
99            }
100            Self::InvalidByteRange { details } => {
101                write!(f, "Invalid ByteRange format: {}", details)
102            }
103            Self::InvalidSignatureDict { details } => {
104                write!(f, "Invalid signature dictionary: {}", details)
105            }
106            Self::ContentsExtractionFailed { details } => {
107                write!(f, "Failed to extract signature contents: {}", details)
108            }
109            Self::AcroFormNotFound => {
110                write!(f, "Document does not contain an AcroForm dictionary")
111            }
112            Self::NoSignatureFields => {
113                write!(f, "No signature fields found in document")
114            }
115            Self::ParseError { message } => {
116                write!(f, "PDF parsing error: {}", message)
117            }
118            Self::CmsParsingFailed { details } => {
119                write!(f, "CMS/PKCS#7 parsing failed: {}", details)
120            }
121            Self::UnsupportedAlgorithm { algorithm } => {
122                write!(f, "Unsupported algorithm: {}", algorithm)
123            }
124            Self::ByteRangeExceedsDocument {
125                offset,
126                length,
127                document_size,
128            } => {
129                write!(
130                    f,
131                    "ByteRange exceeds document: offset {} + length {} > document size {}",
132                    offset, length, document_size
133                )
134            }
135            Self::HashVerificationFailed { details } => {
136                write!(f, "Hash verification failed: {}", details)
137            }
138            Self::SignatureVerificationFailed { details } => {
139                write!(f, "Signature verification failed: {}", details)
140            }
141            Self::CertificateExtractionFailed { details } => {
142                write!(f, "Certificate extraction failed: {}", details)
143            }
144            Self::CertificateValidationFailed { details } => {
145                write!(f, "Certificate validation failed: {}", details)
146            }
147        }
148    }
149}
150
151impl std::error::Error for SignatureError {}
152
153impl From<crate::error::PdfError> for SignatureError {
154    fn from(err: crate::error::PdfError) -> Self {
155        SignatureError::ParseError {
156            message: err.to_string(),
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn test_missing_field_error_display() {
167        let err = SignatureError::MissingField {
168            field: "Filter".to_string(),
169        };
170        assert!(err.to_string().contains("Filter"));
171        assert!(err.to_string().contains("Missing"));
172    }
173
174    #[test]
175    fn test_invalid_byterange_error_display() {
176        let err = SignatureError::InvalidByteRange {
177            details: "expected 4 elements".to_string(),
178        };
179        assert!(err.to_string().contains("ByteRange"));
180        assert!(err.to_string().contains("4 elements"));
181    }
182
183    #[test]
184    fn test_acroform_not_found_error_display() {
185        let err = SignatureError::AcroFormNotFound;
186        assert!(err.to_string().contains("AcroForm"));
187    }
188
189    #[test]
190    fn test_error_is_std_error() {
191        fn assert_error<E: std::error::Error>() {}
192        assert_error::<SignatureError>();
193    }
194
195    #[test]
196    fn test_error_clone_eq() {
197        let err1 = SignatureError::NoSignatureFields;
198        let err2 = err1.clone();
199        assert_eq!(err1, err2);
200    }
201
202    #[test]
203    fn test_all_error_variants_display() {
204        let errors = vec![
205            SignatureError::MissingField {
206                field: "SubFilter".to_string(),
207            },
208            SignatureError::InvalidByteRange {
209                details: "negative value".to_string(),
210            },
211            SignatureError::InvalidSignatureDict {
212                details: "not a dictionary".to_string(),
213            },
214            SignatureError::ContentsExtractionFailed {
215                details: "hex decode failed".to_string(),
216            },
217            SignatureError::AcroFormNotFound,
218            SignatureError::NoSignatureFields,
219            SignatureError::ParseError {
220                message: "unexpected EOF".to_string(),
221            },
222            SignatureError::CmsParsingFailed {
223                details: "invalid DER".to_string(),
224            },
225            SignatureError::UnsupportedAlgorithm {
226                algorithm: "MD5".to_string(),
227            },
228            SignatureError::ByteRangeExceedsDocument {
229                offset: 1000,
230                length: 500,
231                document_size: 800,
232            },
233            SignatureError::HashVerificationFailed {
234                details: "hash mismatch".to_string(),
235            },
236            SignatureError::SignatureVerificationFailed {
237                details: "invalid signature".to_string(),
238            },
239            SignatureError::CertificateExtractionFailed {
240                details: "no certificate".to_string(),
241            },
242            SignatureError::CertificateValidationFailed {
243                details: "certificate expired".to_string(),
244            },
245        ];
246
247        for err in errors {
248            let display = err.to_string();
249            assert!(!display.is_empty(), "Error display should not be empty");
250        }
251    }
252
253    #[test]
254    fn test_cms_parsing_failed_error_display() {
255        let err = SignatureError::CmsParsingFailed {
256            details: "invalid ContentInfo".to_string(),
257        };
258        assert!(err.to_string().contains("CMS"));
259        assert!(err.to_string().contains("invalid ContentInfo"));
260    }
261
262    #[test]
263    fn test_unsupported_algorithm_error_display() {
264        let err = SignatureError::UnsupportedAlgorithm {
265            algorithm: "MD5".to_string(),
266        };
267        assert!(err.to_string().contains("algorithm"));
268        assert!(err.to_string().contains("MD5"));
269    }
270
271    #[test]
272    fn test_byterange_exceeds_document_error_display() {
273        let err = SignatureError::ByteRangeExceedsDocument {
274            offset: 1000,
275            length: 500,
276            document_size: 800,
277        };
278        let display = err.to_string();
279        assert!(display.contains("1000"));
280        assert!(display.contains("500"));
281        assert!(display.contains("800"));
282        assert!(display.contains("exceeds"));
283    }
284
285    #[test]
286    fn test_hash_verification_failed_error_display() {
287        let err = SignatureError::HashVerificationFailed {
288            details: "hash mismatch".to_string(),
289        };
290        assert!(err.to_string().contains("Hash verification failed"));
291        assert!(err.to_string().contains("hash mismatch"));
292    }
293
294    #[test]
295    fn test_signature_verification_failed_error_display() {
296        let err = SignatureError::SignatureVerificationFailed {
297            details: "invalid RSA signature".to_string(),
298        };
299        assert!(err.to_string().contains("Signature verification failed"));
300        assert!(err.to_string().contains("invalid RSA signature"));
301    }
302
303    #[test]
304    fn test_certificate_extraction_failed_error_display() {
305        let err = SignatureError::CertificateExtractionFailed {
306            details: "no certificate found".to_string(),
307        };
308        assert!(err.to_string().contains("Certificate extraction failed"));
309        assert!(err.to_string().contains("no certificate found"));
310    }
311
312    #[test]
313    fn test_certificate_validation_failed_error_display() {
314        let err = SignatureError::CertificateValidationFailed {
315            details: "certificate expired".to_string(),
316        };
317        assert!(err.to_string().contains("Certificate validation failed"));
318        assert!(err.to_string().contains("certificate expired"));
319    }
320}