Skip to main content

pdf_ast/crypto/
signatures.rs

1use super::{CryptoError, CryptoResult, SignatureVerificationResult, TimestampInfo};
2use crate::types::PdfValue;
3
4/// PDF signature handler with support for multiple signature formats
5pub struct PdfSignatureHandler;
6
7impl Default for PdfSignatureHandler {
8    fn default() -> Self {
9        Self::new()
10    }
11}
12
13impl PdfSignatureHandler {
14    pub fn new() -> Self {
15        Self
16    }
17
18    /// Verify a PDF digital signature
19    pub fn verify_pdf_signature(
20        &self,
21        signature_dict: &std::collections::HashMap<String, PdfValue>,
22    ) -> CryptoResult<SignatureVerificationResult> {
23        // Extract signature components
24        let filter = self.extract_filter(signature_dict)?;
25        let contents = self.extract_contents(signature_dict)?;
26        let byte_range = self.extract_byte_range(signature_dict)?;
27
28        match filter.as_str() {
29            "Adobe.PPKLite" | "Adobe.PPKMS" => self.verify_pkcs7_signature(&contents, &byte_range),
30            "Adobe.PPK" => self.verify_x509_rsa_signature(&contents, &byte_range),
31            "ETSI.CAdES.detached" => self.verify_cades_signature(&contents, &byte_range),
32            "ETSI.RFC3161" => self.verify_timestamp_signature(&contents, &byte_range),
33            _ => Err(CryptoError::UnsupportedAlgorithm(format!(
34                "Unsupported signature filter: {}",
35                filter
36            ))),
37        }
38    }
39
40    /// Extract signature filter from signature dictionary
41    fn extract_filter(
42        &self,
43        sig_dict: &std::collections::HashMap<String, PdfValue>,
44    ) -> CryptoResult<String> {
45        match sig_dict.get("Filter") {
46            Some(PdfValue::Name(name)) => Ok(name.without_slash().to_string()),
47            _ => Err(CryptoError::InvalidSignatureFormat(
48                "Missing or invalid Filter".to_string(),
49            )),
50        }
51    }
52
53    /// Extract signature contents (the actual signature bytes)
54    fn extract_contents(
55        &self,
56        sig_dict: &std::collections::HashMap<String, PdfValue>,
57    ) -> CryptoResult<Vec<u8>> {
58        match sig_dict.get("Contents") {
59            Some(PdfValue::String(s)) => {
60                // Contents is typically hex-encoded
61                self.decode_hex_string(s.as_bytes())
62            }
63            _ => Err(CryptoError::InvalidSignatureFormat(
64                "Missing or invalid Contents".to_string(),
65            )),
66        }
67    }
68
69    /// Extract byte range for signature verification
70    fn extract_byte_range(
71        &self,
72        sig_dict: &std::collections::HashMap<String, PdfValue>,
73    ) -> CryptoResult<Vec<u64>> {
74        match sig_dict.get("ByteRange") {
75            Some(PdfValue::Array(arr)) => {
76                let mut byte_range = Vec::new();
77                for item in arr.iter() {
78                    match item {
79                        PdfValue::Integer(i) => byte_range.push(*i as u64),
80                        _ => {
81                            return Err(CryptoError::InvalidSignatureFormat(
82                                "Invalid ByteRange format".to_string(),
83                            ))
84                        }
85                    }
86                }
87                if byte_range.len() != 4 {
88                    return Err(CryptoError::InvalidSignatureFormat(
89                        "ByteRange must have 4 elements".to_string(),
90                    ));
91                }
92                Ok(byte_range)
93            }
94            _ => Err(CryptoError::InvalidSignatureFormat(
95                "Missing or invalid ByteRange".to_string(),
96            )),
97        }
98    }
99
100    /// Decode hex string to bytes
101    fn decode_hex_string(&self, hex_str: &[u8]) -> CryptoResult<Vec<u8>> {
102        let hex_str = std::str::from_utf8(hex_str).map_err(|_| {
103            CryptoError::InvalidSignatureFormat("Invalid UTF-8 in hex string".to_string())
104        })?;
105
106        let hex_str = hex_str.trim_start_matches('<').trim_end_matches('>');
107
108        if hex_str.len() % 2 != 0 {
109            return Err(CryptoError::InvalidSignatureFormat(
110                "Hex string length must be even".to_string(),
111            ));
112        }
113
114        let mut result = Vec::with_capacity(hex_str.len() / 2);
115        for chunk in hex_str.as_bytes().chunks_exact(2) {
116            let hex_byte = std::str::from_utf8(chunk).map_err(|_| {
117                CryptoError::InvalidSignatureFormat("Invalid hex character".to_string())
118            })?;
119            let byte = u8::from_str_radix(hex_byte, 16).map_err(|_| {
120                CryptoError::InvalidSignatureFormat("Invalid hex digit".to_string())
121            })?;
122            result.push(byte);
123        }
124
125        Ok(result)
126    }
127
128    /// Verify PKCS#7 signature (Adobe.PPKLite, Adobe.PPKMS)
129    fn verify_pkcs7_signature(
130        &self,
131        contents: &[u8],
132        byte_range: &[u64],
133    ) -> CryptoResult<SignatureVerificationResult> {
134        #[cfg(feature = "crypto")]
135        {
136            self.verify_pkcs7_with_openssl(contents, byte_range)
137        }
138        #[cfg(not(feature = "crypto"))]
139        {
140            Ok(SignatureVerificationResult {
141                is_valid: false,
142                signer_certificate: None,
143                signing_time: None,
144                algorithm: "PKCS#7".to_string(),
145                error_message: Some("PKCS#7 verification requires crypto feature".to_string()),
146                certificate_chain: Vec::new(),
147                timestamp_info: None,
148            })
149        }
150    }
151
152    /// Verify X.509 RSA signature (Adobe.PPK)
153    fn verify_x509_rsa_signature(
154        &self,
155        contents: &[u8],
156        byte_range: &[u64],
157    ) -> CryptoResult<SignatureVerificationResult> {
158        #[cfg(feature = "crypto")]
159        {
160            self.verify_x509_with_openssl(contents, byte_range)
161        }
162        #[cfg(not(feature = "crypto"))]
163        {
164            Ok(SignatureVerificationResult {
165                is_valid: false,
166                signer_certificate: None,
167                signing_time: None,
168                algorithm: "X.509 RSA".to_string(),
169                error_message: Some("X.509 verification requires crypto feature".to_string()),
170                certificate_chain: Vec::new(),
171                timestamp_info: None,
172            })
173        }
174    }
175
176    /// Verify CAdES signature (ETSI.CAdES.detached)
177    fn verify_cades_signature(
178        &self,
179        contents: &[u8],
180        byte_range: &[u64],
181    ) -> CryptoResult<SignatureVerificationResult> {
182        #[cfg(feature = "crypto")]
183        {
184            self.verify_cades_with_openssl(contents, byte_range)
185        }
186        #[cfg(not(feature = "crypto"))]
187        {
188            Ok(SignatureVerificationResult {
189                is_valid: false,
190                signer_certificate: None,
191                signing_time: None,
192                algorithm: "CAdES".to_string(),
193                error_message: Some("CAdES verification requires crypto feature".to_string()),
194                certificate_chain: Vec::new(),
195                timestamp_info: None,
196            })
197        }
198    }
199
200    /// Verify timestamp signature (ETSI.RFC3161)
201    fn verify_timestamp_signature(
202        &self,
203        contents: &[u8],
204        byte_range: &[u64],
205    ) -> CryptoResult<SignatureVerificationResult> {
206        #[cfg(feature = "crypto")]
207        {
208            self.verify_timestamp_with_openssl(contents, byte_range)
209        }
210        #[cfg(not(feature = "crypto"))]
211        {
212            Ok(SignatureVerificationResult {
213                is_valid: false,
214                signer_certificate: None,
215                signing_time: None,
216                algorithm: "RFC3161 Timestamp".to_string(),
217                error_message: Some("Timestamp verification requires crypto feature".to_string()),
218                certificate_chain: Vec::new(),
219                timestamp_info: None,
220            })
221        }
222    }
223
224    // OpenSSL-based verification methods (only available with crypto feature)
225    #[cfg(feature = "crypto")]
226    fn verify_pkcs7_with_openssl(
227        &self,
228        _contents: &[u8],
229        _byte_range: &[u64],
230    ) -> CryptoResult<SignatureVerificationResult> {
231        // This would implement actual PKCS#7 verification using OpenSSL
232        // For now, return a placeholder
233        Ok(SignatureVerificationResult {
234            is_valid: false,
235            signer_certificate: None,
236            signing_time: None,
237            algorithm: "PKCS#7".to_string(),
238            error_message: Some(
239                "OpenSSL PKCS#7 verification not yet fully implemented".to_string(),
240            ),
241            certificate_chain: Vec::new(),
242            timestamp_info: None,
243        })
244    }
245
246    #[cfg(feature = "crypto")]
247    fn verify_x509_with_openssl(
248        &self,
249        _contents: &[u8],
250        _byte_range: &[u64],
251    ) -> CryptoResult<SignatureVerificationResult> {
252        // This would implement actual X.509 verification using OpenSSL
253        Ok(SignatureVerificationResult {
254            is_valid: false,
255            signer_certificate: None,
256            signing_time: None,
257            algorithm: "X.509 RSA".to_string(),
258            error_message: Some("OpenSSL X.509 verification not yet fully implemented".to_string()),
259            certificate_chain: Vec::new(),
260            timestamp_info: None,
261        })
262    }
263
264    #[cfg(feature = "crypto")]
265    fn verify_cades_with_openssl(
266        &self,
267        _contents: &[u8],
268        _byte_range: &[u64],
269    ) -> CryptoResult<SignatureVerificationResult> {
270        // This would implement CAdES verification
271        Ok(SignatureVerificationResult {
272            is_valid: false,
273            signer_certificate: None,
274            signing_time: None,
275            algorithm: "CAdES".to_string(),
276            error_message: Some("CAdES verification not yet fully implemented".to_string()),
277            certificate_chain: Vec::new(),
278            timestamp_info: None,
279        })
280    }
281
282    #[cfg(feature = "crypto")]
283    fn verify_timestamp_with_openssl(
284        &self,
285        _contents: &[u8],
286        _byte_range: &[u64],
287    ) -> CryptoResult<SignatureVerificationResult> {
288        // This would implement RFC3161 timestamp verification
289        Ok(SignatureVerificationResult {
290            is_valid: false,
291            signer_certificate: None,
292            signing_time: None,
293            algorithm: "RFC3161 Timestamp".to_string(),
294            error_message: Some("RFC3161 verification not yet fully implemented".to_string()),
295            certificate_chain: Vec::new(),
296            timestamp_info: Some(TimestampInfo {
297                timestamp: super::chrono::Utc::now(),
298                timestamp_authority: "Unknown TSA".to_string(),
299                hash_algorithm: "SHA-256".to_string(),
300                is_valid: false,
301                error_message: Some("Not implemented".to_string()),
302            }),
303        })
304    }
305}
306
307/// Signature validation utilities
308pub struct SignatureValidator;
309
310impl SignatureValidator {
311    /// Validate signature dictionary structure
312    pub fn validate_signature_dict(
313        &self,
314        sig_dict: &std::collections::HashMap<String, PdfValue>,
315    ) -> Result<Vec<String>, Vec<String>> {
316        let mut errors = Vec::new();
317        let mut warnings = Vec::new();
318
319        // Required fields
320        if !sig_dict.contains_key("Filter") {
321            errors.push("Missing required Filter field".to_string());
322        }
323        if !sig_dict.contains_key("Contents") {
324            errors.push("Missing required Contents field".to_string());
325        }
326        if !sig_dict.contains_key("ByteRange") {
327            errors.push("Missing required ByteRange field".to_string());
328        }
329
330        // Optional but recommended fields
331        if !sig_dict.contains_key("M") {
332            warnings.push("Missing signing time (M field)".to_string());
333        }
334        if !sig_dict.contains_key("Name") {
335            warnings.push("Missing signer name (Name field)".to_string());
336        }
337        if !sig_dict.contains_key("Reason") {
338            warnings.push("Missing signing reason (Reason field)".to_string());
339        }
340
341        // Validate ByteRange format
342        if let Some(PdfValue::Array(arr)) = sig_dict.get("ByteRange") {
343            if arr.len() != 4 {
344                errors.push("ByteRange must contain exactly 4 integers".to_string());
345            } else {
346                for (i, item) in arr.iter().enumerate() {
347                    if !matches!(item, PdfValue::Integer(_)) {
348                        errors.push(format!("ByteRange[{}] must be an integer", i));
349                    }
350                }
351            }
352        }
353
354        if errors.is_empty() {
355            Ok(warnings)
356        } else {
357            Err(errors)
358        }
359    }
360
361    /// Extract signature metadata from dictionary
362    pub fn extract_signature_metadata(
363        &self,
364        sig_dict: &std::collections::HashMap<String, PdfValue>,
365    ) -> SignatureMetadata {
366        SignatureMetadata {
367            filter: sig_dict
368                .get("Filter")
369                .and_then(|v| v.as_name())
370                .map(|n| n.without_slash().to_string()),
371            sub_filter: sig_dict
372                .get("SubFilter")
373                .and_then(|v| v.as_name())
374                .map(|n| n.without_slash().to_string()),
375            name: sig_dict
376                .get("Name")
377                .and_then(|v| v.as_string())
378                .map(|s| s.to_string_lossy()),
379            location: sig_dict
380                .get("Location")
381                .and_then(|v| v.as_string())
382                .map(|s| s.to_string_lossy()),
383            reason: sig_dict
384                .get("Reason")
385                .and_then(|v| v.as_string())
386                .map(|s| s.to_string_lossy()),
387            contact_info: sig_dict
388                .get("ContactInfo")
389                .and_then(|v| v.as_string())
390                .map(|s| s.to_string_lossy()),
391            signing_time: sig_dict
392                .get("M")
393                .and_then(|v| v.as_string())
394                .map(|s| s.to_string_lossy()),
395        }
396    }
397}
398
399#[derive(Debug, Clone)]
400pub struct SignatureMetadata {
401    pub filter: Option<String>,
402    pub sub_filter: Option<String>,
403    pub name: Option<String>,
404    pub location: Option<String>,
405    pub reason: Option<String>,
406    pub contact_info: Option<String>,
407    pub signing_time: Option<String>,
408}
409
410/// Signature format detector
411pub struct SignatureFormatDetector;
412
413impl SignatureFormatDetector {
414    /// Detect signature format from binary data
415    pub fn detect_format(&self, signature_data: &[u8]) -> SignatureFormat {
416        if signature_data.len() < 10 {
417            return SignatureFormat::Unknown;
418        }
419
420        // Check for ASN.1 DER/BER encoding (PKCS#7, X.509)
421        if signature_data[0] == 0x30 {
422            if self.looks_like_pkcs7(signature_data) {
423                return SignatureFormat::PKCS7;
424            } else if self.looks_like_x509_cert(signature_data) {
425                return SignatureFormat::X509Certificate;
426            }
427        }
428
429        // Check for PEM encoding
430        if signature_data.starts_with(b"-----BEGIN") {
431            if signature_data.starts_with(b"-----BEGIN PKCS7") {
432                return SignatureFormat::Pkcs7Pem;
433            } else if signature_data.starts_with(b"-----BEGIN CERTIFICATE") {
434                return SignatureFormat::X509CertificatePem;
435            }
436        }
437
438        // Check for timestamp token
439        if self.looks_like_timestamp_token(signature_data) {
440            return SignatureFormat::Rfc3161Timestamp;
441        }
442
443        SignatureFormat::Unknown
444    }
445
446    fn looks_like_pkcs7(&self, data: &[u8]) -> bool {
447        // Simplified PKCS#7 detection
448        // Real implementation would parse ASN.1 structure
449        data.len() > 50 && data[0] == 0x30
450    }
451
452    fn looks_like_x509_cert(&self, data: &[u8]) -> bool {
453        // Simplified X.509 certificate detection
454        // Real implementation would parse ASN.1 structure
455        data.len() > 100 && data[0] == 0x30
456    }
457
458    fn looks_like_timestamp_token(&self, data: &[u8]) -> bool {
459        // Simplified timestamp token detection
460        // Real implementation would look for specific OIDs
461        data.len() > 20 && data[0] == 0x30
462    }
463}
464
465#[derive(Debug, Clone, PartialEq)]
466pub enum SignatureFormat {
467    PKCS7,
468    Pkcs7Pem,
469    X509Certificate,
470    X509CertificatePem,
471    Rfc3161Timestamp,
472    CAdES,
473    PAdES,
474    Unknown,
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480    use crate::types::{PdfArray, PdfName, PdfString};
481    use std::collections::HashMap;
482
483    #[test]
484    fn test_signature_validator() {
485        let validator = SignatureValidator;
486        let mut sig_dict = HashMap::new();
487
488        // Test missing required fields
489        let result = validator.validate_signature_dict(&sig_dict);
490        assert!(result.is_err());
491
492        // Add required fields
493        sig_dict.insert(
494            "Filter".to_string(),
495            PdfValue::Name(PdfName::new("Adobe.PPKLite")),
496        );
497        sig_dict.insert(
498            "Contents".to_string(),
499            PdfValue::String(PdfString::new_literal(b"<3082...")),
500        );
501
502        let mut byte_range = PdfArray::new();
503        byte_range.push(PdfValue::Integer(0));
504        byte_range.push(PdfValue::Integer(1000));
505        byte_range.push(PdfValue::Integer(2000));
506        byte_range.push(PdfValue::Integer(3000));
507        sig_dict.insert("ByteRange".to_string(), PdfValue::Array(byte_range));
508
509        let result = validator.validate_signature_dict(&sig_dict);
510        assert!(result.is_ok());
511    }
512
513    #[test]
514    fn test_hex_string_decoding() {
515        let handler = PdfSignatureHandler::new();
516
517        let hex_str = b"<48656C6C6F>";
518        let result = handler.decode_hex_string(hex_str).unwrap();
519        assert_eq!(result, b"Hello");
520
521        let invalid_hex = b"<GG>";
522        assert!(handler.decode_hex_string(invalid_hex).is_err());
523    }
524
525    #[test]
526    fn test_signature_format_detection() {
527        let detector = SignatureFormatDetector;
528
529        // Test ASN.1 DER format with sufficient length
530        // The original test expected either PKCS7 or X509Certificate for ASN.1 data
531        let mut der_data = vec![0x30, 0x82, 0x01, 0x00]; // ASN.1 SEQUENCE header
532        der_data.resize(60, 0x00); // Make it large enough to be detected
533        let format = detector.detect_format(&der_data);
534        // The current implementation will detect this as PKCS7 since it checks PKCS7 first
535        assert!(matches!(
536            format,
537            SignatureFormat::PKCS7 | SignatureFormat::X509Certificate
538        ));
539
540        // Test with data that's too small to be detected as either PKCS7 or X509
541        let small_der_data = [0x30, 0x82, 0x01, 0x00]; // Only 4 bytes
542        let format = detector.detect_format(&small_der_data);
543        assert_eq!(format, SignatureFormat::Unknown);
544
545        // Test PEM format
546        let pem_data = b"-----BEGIN CERTIFICATE-----";
547        let format = detector.detect_format(pem_data);
548        assert_eq!(format, SignatureFormat::X509CertificatePem);
549
550        // Test unknown format
551        let unknown_data = b"unknown";
552        let format = detector.detect_format(unknown_data);
553        assert_eq!(format, SignatureFormat::Unknown);
554    }
555}