oxidize_pdf/encryption/
crypt_filters.rs

1//! Crypt Filters implementation for PDF encryption
2//!
3//! This module implements functional crypt filters according to ISO 32000-1:2008
4//! Section 7.6.5, supporting selective encryption of streams and strings.
5
6use crate::encryption::{CryptFilterMethod, EncryptionKey, StandardSecurityHandler};
7use crate::error::{PdfError, Result};
8use crate::objects::{Dictionary, Object, ObjectId};
9use std::collections::HashMap;
10
11/// Functional Crypt Filter implementation
12#[derive(Debug, Clone)]
13pub struct FunctionalCryptFilter {
14    /// Filter name
15    pub name: String,
16    /// Encryption method
17    pub method: CryptFilterMethod,
18    /// Length in bytes (for RC4)
19    pub length: Option<u32>,
20    /// Authentication event (when filter is applied)
21    pub auth_event: AuthEvent,
22    /// Recipients (for public key)
23    pub recipients: Option<Vec<String>>,
24}
25
26/// Authentication event for crypt filters
27#[derive(Debug, Clone, Copy, PartialEq)]
28pub enum AuthEvent {
29    /// Apply filter when document is opened
30    DocOpen,
31    /// Apply filter for embedded files only
32    EFOpen,
33}
34
35impl AuthEvent {
36    /// Get PDF name
37    pub fn pdf_name(&self) -> &'static str {
38        match self {
39            AuthEvent::DocOpen => "DocOpen",
40            AuthEvent::EFOpen => "EFOpen",
41        }
42    }
43}
44
45/// Crypt Filter Manager
46pub struct CryptFilterManager {
47    /// Map of filter names to filters
48    filters: HashMap<String, FunctionalCryptFilter>,
49    /// Default filter for streams
50    default_stream_filter: String,
51    /// Default filter for strings
52    default_string_filter: String,
53    /// Embedded files filter
54    embedded_files_filter: Option<String>,
55    /// Security handler
56    security_handler: Box<dyn SecurityHandler>,
57}
58
59impl CryptFilterManager {
60    /// Create new crypt filter manager
61    pub fn new(
62        security_handler: Box<dyn SecurityHandler>,
63        default_stream_filter: String,
64        default_string_filter: String,
65    ) -> Self {
66        let mut manager = Self {
67            filters: HashMap::new(),
68            default_stream_filter,
69            default_string_filter,
70            embedded_files_filter: None,
71            security_handler,
72        };
73
74        // Add identity filter (no encryption)
75        manager.add_filter(FunctionalCryptFilter {
76            name: "Identity".to_string(),
77            method: CryptFilterMethod::None,
78            length: None,
79            auth_event: AuthEvent::DocOpen,
80            recipients: None,
81        });
82
83        manager
84    }
85
86    /// Add a crypt filter
87    pub fn add_filter(&mut self, filter: FunctionalCryptFilter) {
88        self.filters.insert(filter.name.clone(), filter);
89    }
90
91    /// Set embedded files filter
92    pub fn set_embedded_files_filter(&mut self, filter_name: String) {
93        self.embedded_files_filter = Some(filter_name);
94    }
95
96    /// Get filter by name
97    pub fn get_filter(&self, name: &str) -> Option<&FunctionalCryptFilter> {
98        self.filters.get(name)
99    }
100
101    /// Encrypt string with appropriate filter
102    pub fn encrypt_string(
103        &self,
104        data: &[u8],
105        obj_id: &ObjectId,
106        filter_name: Option<&str>,
107        encryption_key: &EncryptionKey,
108    ) -> Result<Vec<u8>> {
109        let filter_name = filter_name.unwrap_or(&self.default_string_filter);
110        let filter = self.get_filter(filter_name).ok_or_else(|| {
111            PdfError::EncryptionError(format!("Crypt filter '{filter_name}' not found"))
112        })?;
113
114        match filter.method {
115            CryptFilterMethod::None => Ok(data.to_vec()),
116            CryptFilterMethod::V2 => {
117                // RC4 encryption
118                self.security_handler
119                    .encrypt_string(data, encryption_key, obj_id)
120            }
121            CryptFilterMethod::AESV2 => {
122                // AES-128 encryption
123                self.security_handler
124                    .encrypt_string_aes(data, encryption_key, obj_id, 128)
125            }
126            CryptFilterMethod::AESV3 => {
127                // AES-256 encryption
128                self.security_handler
129                    .encrypt_string_aes(data, encryption_key, obj_id, 256)
130            }
131        }
132    }
133
134    /// Decrypt string with appropriate filter
135    pub fn decrypt_string(
136        &self,
137        data: &[u8],
138        obj_id: &ObjectId,
139        filter_name: Option<&str>,
140        encryption_key: &EncryptionKey,
141    ) -> Result<Vec<u8>> {
142        let filter_name = filter_name.unwrap_or(&self.default_string_filter);
143        let filter = self.get_filter(filter_name).ok_or_else(|| {
144            PdfError::EncryptionError(format!("Crypt filter '{filter_name}' not found"))
145        })?;
146
147        match filter.method {
148            CryptFilterMethod::None => Ok(data.to_vec()),
149            CryptFilterMethod::V2 => {
150                // RC4 decryption
151                self.security_handler
152                    .decrypt_string(data, encryption_key, obj_id)
153            }
154            CryptFilterMethod::AESV2 => {
155                // AES-128 decryption
156                self.security_handler
157                    .decrypt_string_aes(data, encryption_key, obj_id, 128)
158            }
159            CryptFilterMethod::AESV3 => {
160                // AES-256 decryption
161                self.security_handler
162                    .decrypt_string_aes(data, encryption_key, obj_id, 256)
163            }
164        }
165    }
166
167    /// Encrypt stream with appropriate filter
168    pub fn encrypt_stream(
169        &self,
170        data: &[u8],
171        obj_id: &ObjectId,
172        stream_dict: &Dictionary,
173        encryption_key: &EncryptionKey,
174    ) -> Result<Vec<u8>> {
175        // Check if stream has a specific filter
176        let filter_name = self.get_stream_filter_name(stream_dict);
177        let filter = self.get_filter(&filter_name).ok_or_else(|| {
178            PdfError::EncryptionError(format!("Crypt filter '{filter_name}' not found"))
179        })?;
180
181        match filter.method {
182            CryptFilterMethod::None => Ok(data.to_vec()),
183            CryptFilterMethod::V2 => {
184                // RC4 encryption
185                self.security_handler
186                    .encrypt_stream(data, encryption_key, obj_id)
187            }
188            CryptFilterMethod::AESV2 => {
189                // AES-128 encryption
190                self.security_handler
191                    .encrypt_stream_aes(data, encryption_key, obj_id, 128)
192            }
193            CryptFilterMethod::AESV3 => {
194                // AES-256 encryption
195                self.security_handler
196                    .encrypt_stream_aes(data, encryption_key, obj_id, 256)
197            }
198        }
199    }
200
201    /// Decrypt stream with appropriate filter
202    pub fn decrypt_stream(
203        &self,
204        data: &[u8],
205        obj_id: &ObjectId,
206        stream_dict: &Dictionary,
207        encryption_key: &EncryptionKey,
208    ) -> Result<Vec<u8>> {
209        // Check if stream has a specific filter
210        let filter_name = self.get_stream_filter_name(stream_dict);
211        let filter = self.get_filter(&filter_name).ok_or_else(|| {
212            PdfError::EncryptionError(format!("Crypt filter '{filter_name}' not found"))
213        })?;
214
215        match filter.method {
216            CryptFilterMethod::None => Ok(data.to_vec()),
217            CryptFilterMethod::V2 => {
218                // RC4 decryption
219                self.security_handler
220                    .decrypt_stream(data, encryption_key, obj_id)
221            }
222            CryptFilterMethod::AESV2 => {
223                // AES-128 decryption
224                self.security_handler
225                    .decrypt_stream_aes(data, encryption_key, obj_id, 128)
226            }
227            CryptFilterMethod::AESV3 => {
228                // AES-256 decryption
229                self.security_handler
230                    .decrypt_stream_aes(data, encryption_key, obj_id, 256)
231            }
232        }
233    }
234
235    /// Get stream filter name from dictionary
236    fn get_stream_filter_name(&self, stream_dict: &Dictionary) -> String {
237        // Check for Filter array
238        if let Some(Object::Array(filters)) = stream_dict.get("Filter") {
239            // Look for Crypt filter in array
240            for filter in filters {
241                if let Object::Name(name) = filter {
242                    if name == "Crypt" {
243                        // Check DecodeParms for crypt filter name
244                        if let Some(Object::Dictionary(decode_parms)) =
245                            stream_dict.get("DecodeParms")
246                        {
247                            if let Some(Object::Name(crypt_name)) = decode_parms.get("Name") {
248                                return crypt_name.clone();
249                            }
250                        }
251                    }
252                }
253            }
254        }
255
256        // Use default stream filter
257        self.default_stream_filter.clone()
258    }
259
260    /// Create StdCF filter for standard encryption
261    pub fn create_standard_filter(
262        method: CryptFilterMethod,
263        key_length: Option<u32>,
264    ) -> FunctionalCryptFilter {
265        FunctionalCryptFilter {
266            name: "StdCF".to_string(),
267            method,
268            length: key_length,
269            auth_event: AuthEvent::DocOpen,
270            recipients: None,
271        }
272    }
273
274    /// Convert filters to PDF dictionary
275    pub fn to_cf_dict(&self) -> Dictionary {
276        let mut cf_dict = Dictionary::new();
277
278        for (name, filter) in &self.filters {
279            if name != "Identity" {
280                cf_dict.set(name, Object::Dictionary(filter.to_dict()));
281            }
282        }
283
284        cf_dict
285    }
286}
287
288impl FunctionalCryptFilter {
289    /// Convert to PDF dictionary
290    pub fn to_dict(&self) -> Dictionary {
291        let mut dict = Dictionary::new();
292
293        dict.set("CFM", Object::Name(self.method.pdf_name().to_string()));
294
295        if let Some(length) = self.length {
296            dict.set("Length", Object::Integer(length as i64));
297        }
298
299        dict.set(
300            "AuthEvent",
301            Object::Name(self.auth_event.pdf_name().to_string()),
302        );
303
304        if let Some(ref recipients) = self.recipients {
305            let recipient_array: Vec<Object> = recipients
306                .iter()
307                .map(|r| Object::String(r.clone()))
308                .collect();
309            dict.set("Recipients", Object::Array(recipient_array));
310        }
311
312        dict
313    }
314}
315
316/// Security Handler trait for encryption/decryption operations
317pub trait SecurityHandler: Send + Sync {
318    /// Encrypt string
319    fn encrypt_string(
320        &self,
321        data: &[u8],
322        key: &EncryptionKey,
323        obj_id: &ObjectId,
324    ) -> Result<Vec<u8>>;
325
326    /// Decrypt string
327    fn decrypt_string(
328        &self,
329        data: &[u8],
330        key: &EncryptionKey,
331        obj_id: &ObjectId,
332    ) -> Result<Vec<u8>>;
333
334    /// Encrypt stream
335    fn encrypt_stream(
336        &self,
337        data: &[u8],
338        key: &EncryptionKey,
339        obj_id: &ObjectId,
340    ) -> Result<Vec<u8>>;
341
342    /// Decrypt stream
343    fn decrypt_stream(
344        &self,
345        data: &[u8],
346        key: &EncryptionKey,
347        obj_id: &ObjectId,
348    ) -> Result<Vec<u8>>;
349
350    /// Encrypt string with AES
351    fn encrypt_string_aes(
352        &self,
353        data: &[u8],
354        key: &EncryptionKey,
355        obj_id: &ObjectId,
356        bits: u32,
357    ) -> Result<Vec<u8>>;
358
359    /// Decrypt string with AES
360    fn decrypt_string_aes(
361        &self,
362        data: &[u8],
363        key: &EncryptionKey,
364        obj_id: &ObjectId,
365        bits: u32,
366    ) -> Result<Vec<u8>>;
367
368    /// Encrypt stream with AES
369    fn encrypt_stream_aes(
370        &self,
371        data: &[u8],
372        key: &EncryptionKey,
373        obj_id: &ObjectId,
374        bits: u32,
375    ) -> Result<Vec<u8>>;
376
377    /// Decrypt stream with AES
378    fn decrypt_stream_aes(
379        &self,
380        data: &[u8],
381        key: &EncryptionKey,
382        obj_id: &ObjectId,
383        bits: u32,
384    ) -> Result<Vec<u8>>;
385}
386
387/// Standard Security Handler implementation
388impl SecurityHandler for StandardSecurityHandler {
389    fn encrypt_string(
390        &self,
391        data: &[u8],
392        key: &EncryptionKey,
393        obj_id: &ObjectId,
394    ) -> Result<Vec<u8>> {
395        Ok(self.encrypt_string(data, key, obj_id))
396    }
397
398    fn decrypt_string(
399        &self,
400        data: &[u8],
401        key: &EncryptionKey,
402        obj_id: &ObjectId,
403    ) -> Result<Vec<u8>> {
404        Ok(self.decrypt_string(data, key, obj_id))
405    }
406
407    fn encrypt_stream(
408        &self,
409        data: &[u8],
410        key: &EncryptionKey,
411        obj_id: &ObjectId,
412    ) -> Result<Vec<u8>> {
413        Ok(self.encrypt_stream(data, key, obj_id))
414    }
415
416    fn decrypt_stream(
417        &self,
418        data: &[u8],
419        key: &EncryptionKey,
420        obj_id: &ObjectId,
421    ) -> Result<Vec<u8>> {
422        Ok(self.decrypt_stream(data, key, obj_id))
423    }
424
425    fn encrypt_string_aes(
426        &self,
427        data: &[u8],
428        key: &EncryptionKey,
429        obj_id: &ObjectId,
430        bits: u32,
431    ) -> Result<Vec<u8>> {
432        match bits {
433            128 | 256 => {
434                // For AES, use the same method as streams
435                self.encrypt_aes(data, key, obj_id)
436            }
437            _ => Err(PdfError::EncryptionError(format!(
438                "Unsupported AES key size: {bits} bits"
439            ))),
440        }
441    }
442
443    fn decrypt_string_aes(
444        &self,
445        data: &[u8],
446        key: &EncryptionKey,
447        obj_id: &ObjectId,
448        bits: u32,
449    ) -> Result<Vec<u8>> {
450        match bits {
451            128 | 256 => {
452                // For AES, use the same method as streams
453                self.decrypt_aes(data, key, obj_id)
454            }
455            _ => Err(PdfError::EncryptionError(format!(
456                "Unsupported AES key size: {bits} bits"
457            ))),
458        }
459    }
460
461    fn encrypt_stream_aes(
462        &self,
463        data: &[u8],
464        key: &EncryptionKey,
465        obj_id: &ObjectId,
466        bits: u32,
467    ) -> Result<Vec<u8>> {
468        self.encrypt_string_aes(data, key, obj_id, bits)
469    }
470
471    fn decrypt_stream_aes(
472        &self,
473        data: &[u8],
474        key: &EncryptionKey,
475        obj_id: &ObjectId,
476        bits: u32,
477    ) -> Result<Vec<u8>> {
478        self.decrypt_string_aes(data, key, obj_id, bits)
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    #[test]
487    fn test_auth_event_pdf_names() {
488        assert_eq!(AuthEvent::DocOpen.pdf_name(), "DocOpen");
489        assert_eq!(AuthEvent::EFOpen.pdf_name(), "EFOpen");
490    }
491
492    #[test]
493    fn test_functional_crypt_filter_creation() {
494        let filter = FunctionalCryptFilter {
495            name: "TestFilter".to_string(),
496            method: CryptFilterMethod::AESV2,
497            length: Some(16),
498            auth_event: AuthEvent::DocOpen,
499            recipients: None,
500        };
501
502        assert_eq!(filter.name, "TestFilter");
503        assert_eq!(filter.method, CryptFilterMethod::AESV2);
504        assert_eq!(filter.length, Some(16));
505        assert_eq!(filter.auth_event, AuthEvent::DocOpen);
506    }
507
508    #[test]
509    fn test_crypt_filter_to_dict() {
510        let filter = FunctionalCryptFilter {
511            name: "MyFilter".to_string(),
512            method: CryptFilterMethod::V2,
513            length: Some(16),
514            auth_event: AuthEvent::EFOpen,
515            recipients: Some(vec!["recipient1".to_string(), "recipient2".to_string()]),
516        };
517
518        let dict = filter.to_dict();
519        assert_eq!(dict.get("CFM"), Some(&Object::Name("V2".to_string())));
520        assert_eq!(dict.get("Length"), Some(&Object::Integer(16)));
521        assert_eq!(
522            dict.get("AuthEvent"),
523            Some(&Object::Name("EFOpen".to_string()))
524        );
525        assert!(dict.get("Recipients").is_some());
526    }
527
528    #[test]
529    fn test_crypt_filter_manager_creation() {
530        let handler = StandardSecurityHandler::rc4_128bit();
531        let manager =
532            CryptFilterManager::new(Box::new(handler), "StdCF".to_string(), "StdCF".to_string());
533
534        // Should have Identity filter by default
535        assert!(manager.get_filter("Identity").is_some());
536    }
537
538    #[test]
539    fn test_add_and_get_filter() {
540        let handler = StandardSecurityHandler::rc4_128bit();
541        let mut manager =
542            CryptFilterManager::new(Box::new(handler), "StdCF".to_string(), "StdCF".to_string());
543
544        let filter = FunctionalCryptFilter {
545            name: "CustomFilter".to_string(),
546            method: CryptFilterMethod::AESV3,
547            length: None,
548            auth_event: AuthEvent::DocOpen,
549            recipients: None,
550        };
551
552        manager.add_filter(filter);
553
554        let retrieved = manager.get_filter("CustomFilter");
555        assert!(retrieved.is_some());
556        assert_eq!(retrieved.unwrap().method, CryptFilterMethod::AESV3);
557    }
558
559    #[test]
560    fn test_standard_filter_creation() {
561        let filter = CryptFilterManager::create_standard_filter(CryptFilterMethod::AESV2, Some(16));
562
563        assert_eq!(filter.name, "StdCF");
564        assert_eq!(filter.method, CryptFilterMethod::AESV2);
565        assert_eq!(filter.length, Some(16));
566        assert_eq!(filter.auth_event, AuthEvent::DocOpen);
567    }
568
569    #[test]
570    fn test_encrypt_decrypt_string_identity() {
571        let handler = StandardSecurityHandler::rc4_128bit();
572        let manager = CryptFilterManager::new(
573            Box::new(handler),
574            "Identity".to_string(),
575            "Identity".to_string(),
576        );
577
578        let data = b"Test data";
579        let obj_id = ObjectId::new(1, 0);
580        let key = EncryptionKey::new(vec![0u8; 16]);
581
582        let encrypted = manager.encrypt_string(data, &obj_id, None, &key).unwrap();
583        assert_eq!(encrypted, data);
584
585        let decrypted = manager
586            .decrypt_string(&encrypted, &obj_id, None, &key)
587            .unwrap();
588        assert_eq!(decrypted, data);
589    }
590
591    #[test]
592    fn test_set_embedded_files_filter() {
593        let handler = StandardSecurityHandler::rc4_128bit();
594        let mut manager =
595            CryptFilterManager::new(Box::new(handler), "StdCF".to_string(), "StdCF".to_string());
596
597        manager.set_embedded_files_filter("EFFFilter".to_string());
598        assert_eq!(manager.embedded_files_filter, Some("EFFFilter".to_string()));
599    }
600
601    #[test]
602    fn test_to_cf_dict() {
603        let handler = StandardSecurityHandler::rc4_128bit();
604        let mut manager =
605            CryptFilterManager::new(Box::new(handler), "StdCF".to_string(), "StdCF".to_string());
606
607        let filter1 = FunctionalCryptFilter {
608            name: "Filter1".to_string(),
609            method: CryptFilterMethod::V2,
610            length: Some(16),
611            auth_event: AuthEvent::DocOpen,
612            recipients: None,
613        };
614
615        let filter2 = FunctionalCryptFilter {
616            name: "Filter2".to_string(),
617            method: CryptFilterMethod::AESV2,
618            length: None,
619            auth_event: AuthEvent::EFOpen,
620            recipients: None,
621        };
622
623        manager.add_filter(filter1);
624        manager.add_filter(filter2);
625
626        let cf_dict = manager.to_cf_dict();
627
628        // Should not include Identity filter
629        assert!(cf_dict.get("Identity").is_none());
630
631        // Should include other filters
632        assert!(cf_dict.get("Filter1").is_some());
633        assert!(cf_dict.get("Filter2").is_some());
634    }
635
636    #[test]
637    fn test_get_stream_filter_name_default() {
638        let handler = StandardSecurityHandler::rc4_128bit();
639        let manager = CryptFilterManager::new(
640            Box::new(handler),
641            "DefaultStreamFilter".to_string(),
642            "StdCF".to_string(),
643        );
644
645        let stream_dict = Dictionary::new();
646        let filter_name = manager.get_stream_filter_name(&stream_dict);
647        assert_eq!(filter_name, "DefaultStreamFilter");
648    }
649
650    #[test]
651    fn test_get_stream_filter_name_with_crypt() {
652        let handler = StandardSecurityHandler::rc4_128bit();
653        let manager = CryptFilterManager::new(
654            Box::new(handler),
655            "DefaultStreamFilter".to_string(),
656            "StdCF".to_string(),
657        );
658
659        let mut stream_dict = Dictionary::new();
660        let filters = vec![
661            Object::Name("FlateDecode".to_string()),
662            Object::Name("Crypt".to_string()),
663        ];
664        stream_dict.set("Filter", Object::Array(filters));
665
666        let mut decode_parms = Dictionary::new();
667        decode_parms.set("Name", Object::Name("SpecialFilter".to_string()));
668        stream_dict.set("DecodeParms", Object::Dictionary(decode_parms));
669
670        let filter_name = manager.get_stream_filter_name(&stream_dict);
671        assert_eq!(filter_name, "SpecialFilter");
672    }
673
674    #[test]
675    fn test_auth_event_equality() {
676        assert_eq!(AuthEvent::DocOpen, AuthEvent::DocOpen);
677        assert_eq!(AuthEvent::EFOpen, AuthEvent::EFOpen);
678        assert_ne!(AuthEvent::DocOpen, AuthEvent::EFOpen);
679    }
680
681    #[test]
682    fn test_crypt_filter_with_recipients() {
683        let recipients = vec![
684            "user1@example.com".to_string(),
685            "user2@example.com".to_string(),
686        ];
687
688        let filter = FunctionalCryptFilter {
689            name: "PublicKeyFilter".to_string(),
690            method: CryptFilterMethod::AESV3,
691            length: None,
692            auth_event: AuthEvent::DocOpen,
693            recipients: Some(recipients),
694        };
695
696        let dict = filter.to_dict();
697
698        if let Some(Object::Array(recipient_array)) = dict.get("Recipients") {
699            assert_eq!(recipient_array.len(), 2);
700        } else {
701            panic!("Recipients not found in dictionary");
702        }
703    }
704
705    #[test]
706    fn test_filter_not_found_error() {
707        let handler = StandardSecurityHandler::rc4_128bit();
708        let manager =
709            CryptFilterManager::new(Box::new(handler), "StdCF".to_string(), "StdCF".to_string());
710
711        let obj_id = ObjectId::new(1, 0);
712        let key = EncryptionKey::new(vec![0u8; 16]);
713
714        // Try to use non-existent filter
715        let result = manager.encrypt_string(b"test", &obj_id, Some("NonExistentFilter"), &key);
716
717        assert!(result.is_err());
718        if let Err(PdfError::EncryptionError(msg)) = result {
719            assert!(msg.contains("not found"));
720        }
721    }
722}