Skip to main content

oxidize_pdf/encryption/
embedded_files.rs

1//! Embedded files encryption support for PDF
2//!
3//! This module implements encryption control for embedded files and metadata
4//! according to ISO 32000-1:2008 ยง7.6.5.
5
6use crate::encryption::{CryptFilterManager, EncryptionKey};
7use crate::error::Result;
8use crate::objects::{Dictionary, Object, ObjectId};
9use std::sync::Arc;
10
11/// Embedded file encryption handler
12pub struct EmbeddedFileEncryption {
13    /// Filter name for embedded files (EFF)
14    eff_filter: Option<String>,
15    /// Whether to encrypt metadata
16    encrypt_metadata: bool,
17    /// Crypt filter manager
18    filter_manager: Arc<CryptFilterManager>,
19}
20
21impl EmbeddedFileEncryption {
22    /// Create new embedded file encryption handler
23    pub fn new(
24        eff_filter: Option<String>,
25        encrypt_metadata: bool,
26        filter_manager: Arc<CryptFilterManager>,
27    ) -> Self {
28        Self {
29            eff_filter,
30            encrypt_metadata,
31            filter_manager,
32        }
33    }
34
35    /// Check if a stream is an embedded file
36    pub fn is_embedded_file(stream_dict: &Dictionary) -> bool {
37        if let Some(Object::Name(type_name)) = stream_dict.get("Type") {
38            type_name == "EmbeddedFile"
39        } else {
40            false
41        }
42    }
43
44    /// Check if a stream is metadata
45    pub fn is_metadata(stream_dict: &Dictionary) -> bool {
46        if let Some(Object::Name(type_name)) = stream_dict.get("Type") {
47            type_name == "Metadata"
48        } else if let Some(Object::Name(subtype)) = stream_dict.get("Subtype") {
49            subtype == "XML" && stream_dict.contains_key("Metadata")
50        } else {
51            false
52        }
53    }
54
55    /// Get the appropriate filter for a stream
56    pub fn get_stream_filter(&self, stream_dict: &Dictionary) -> Option<String> {
57        if Self::is_embedded_file(stream_dict) {
58            // Use EFF filter if configured
59            self.eff_filter.clone()
60        } else if Self::is_metadata(stream_dict) && !self.encrypt_metadata {
61            // Don't encrypt metadata if flag is false
62            Some("Identity".to_string())
63        } else {
64            // Use default stream filter
65            None
66        }
67    }
68
69    /// Encrypt embedded file data
70    pub fn encrypt_embedded_file(
71        &self,
72        data: &[u8],
73        obj_id: &ObjectId,
74        encryption_key: &EncryptionKey,
75    ) -> Result<Vec<u8>> {
76        if let Some(ref _filter_name) = self.eff_filter {
77            self.filter_manager.encrypt_stream(
78                data,
79                obj_id,
80                &Dictionary::new(), // Empty dict for embedded files
81                encryption_key,
82            )
83        } else {
84            // No EFF filter configured, return data as-is
85            Ok(data.to_vec())
86        }
87    }
88
89    /// Decrypt embedded file data
90    pub fn decrypt_embedded_file(
91        &self,
92        data: &[u8],
93        obj_id: &ObjectId,
94        encryption_key: &EncryptionKey,
95    ) -> Result<Vec<u8>> {
96        if let Some(ref _filter_name) = self.eff_filter {
97            self.filter_manager.decrypt_stream(
98                data,
99                obj_id,
100                &Dictionary::new(), // Empty dict for embedded files
101                encryption_key,
102            )
103        } else {
104            // No EFF filter configured, return data as-is
105            Ok(data.to_vec())
106        }
107    }
108
109    /// Process a stream for encryption, considering embedded files and metadata
110    pub fn process_stream_encryption(
111        &self,
112        stream_dict: &Dictionary,
113        data: &[u8],
114        obj_id: &ObjectId,
115        encryption_key: &EncryptionKey,
116        encrypt: bool,
117    ) -> Result<Vec<u8>> {
118        // Check if this is an embedded file
119        if Self::is_embedded_file(stream_dict) {
120            if encrypt {
121                self.encrypt_embedded_file(data, obj_id, encryption_key)
122            } else {
123                self.decrypt_embedded_file(data, obj_id, encryption_key)
124            }
125        } else if Self::is_metadata(stream_dict) && !self.encrypt_metadata {
126            // Don't encrypt/decrypt metadata if flag is false
127            Ok(data.to_vec())
128        } else {
129            // Use standard stream encryption
130            if encrypt {
131                self.filter_manager
132                    .encrypt_stream(data, obj_id, stream_dict, encryption_key)
133            } else {
134                self.filter_manager
135                    .decrypt_stream(data, obj_id, stream_dict, encryption_key)
136            }
137        }
138    }
139}
140
141/// Extended encryption dictionary with EFF support
142pub struct ExtendedEncryptionDict {
143    /// Base encryption dictionary fields
144    pub base: crate::encryption::EncryptionDictionary,
145    /// Embedded files filter (EFF)
146    pub eff: Option<String>,
147}
148
149impl ExtendedEncryptionDict {
150    /// Create from base dictionary with EFF
151    pub fn new(base: crate::encryption::EncryptionDictionary, eff: Option<String>) -> Self {
152        Self { base, eff }
153    }
154
155    /// Convert to PDF dictionary with EFF field
156    pub fn to_dict(&self) -> Dictionary {
157        let mut dict = self.base.to_dict();
158
159        // Add EFF field if present
160        if let Some(ref eff) = self.eff {
161            dict.set("EFF", Object::Name(eff.clone()));
162        }
163
164        dict
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use crate::encryption::{
172        AuthEvent, CryptFilterMethod, FunctionalCryptFilter, StandardSecurityHandler,
173    };
174
175    fn create_test_filter_manager() -> Arc<CryptFilterManager> {
176        let handler = Box::new(StandardSecurityHandler::rc4_128bit());
177        let mut manager =
178            CryptFilterManager::new(handler, "StdCF".to_string(), "StdCF".to_string());
179
180        // Add standard filter
181        manager.add_filter(FunctionalCryptFilter {
182            name: "StdCF".to_string(),
183            method: CryptFilterMethod::V2,
184            length: Some(16),
185            auth_event: AuthEvent::DocOpen,
186            recipients: None,
187        });
188
189        // Add EFF filter
190        manager.add_filter(FunctionalCryptFilter {
191            name: "EmbeddedFileFilter".to_string(),
192            method: CryptFilterMethod::AESV2,
193            length: None,
194            auth_event: AuthEvent::EFOpen,
195            recipients: None,
196        });
197
198        Arc::new(manager)
199    }
200
201    #[test]
202    fn test_is_embedded_file() {
203        let mut dict = Dictionary::new();
204        assert!(!EmbeddedFileEncryption::is_embedded_file(&dict));
205
206        dict.set("Type", Object::Name("EmbeddedFile".to_string()));
207        assert!(EmbeddedFileEncryption::is_embedded_file(&dict));
208
209        dict.set("Type", Object::Name("Stream".to_string()));
210        assert!(!EmbeddedFileEncryption::is_embedded_file(&dict));
211    }
212
213    #[test]
214    fn test_is_metadata() {
215        let mut dict = Dictionary::new();
216        assert!(!EmbeddedFileEncryption::is_metadata(&dict));
217
218        // Type-based metadata
219        dict.set("Type", Object::Name("Metadata".to_string()));
220        assert!(EmbeddedFileEncryption::is_metadata(&dict));
221
222        // Subtype-based metadata
223        let mut dict2 = Dictionary::new();
224        dict2.set("Subtype", Object::Name("XML".to_string()));
225        dict2.set("Metadata", Object::Null);
226        assert!(EmbeddedFileEncryption::is_metadata(&dict2));
227    }
228
229    #[test]
230    fn test_get_stream_filter() {
231        let filter_manager = create_test_filter_manager();
232        let handler = EmbeddedFileEncryption::new(
233            Some("EmbeddedFileFilter".to_string()),
234            false,
235            filter_manager,
236        );
237
238        // Test embedded file
239        let mut ef_dict = Dictionary::new();
240        ef_dict.set("Type", Object::Name("EmbeddedFile".to_string()));
241        assert_eq!(
242            handler.get_stream_filter(&ef_dict),
243            Some("EmbeddedFileFilter".to_string())
244        );
245
246        // Test metadata (encrypt_metadata = false)
247        let mut meta_dict = Dictionary::new();
248        meta_dict.set("Type", Object::Name("Metadata".to_string()));
249        assert_eq!(
250            handler.get_stream_filter(&meta_dict),
251            Some("Identity".to_string())
252        );
253
254        // Test regular stream
255        let regular_dict = Dictionary::new();
256        assert_eq!(handler.get_stream_filter(&regular_dict), None);
257    }
258
259    #[test]
260    fn test_get_stream_filter_with_metadata_encryption() {
261        let filter_manager = create_test_filter_manager();
262        let handler = EmbeddedFileEncryption::new(
263            Some("EmbeddedFileFilter".to_string()),
264            true, // encrypt_metadata = true
265            filter_manager,
266        );
267
268        // Test metadata with encryption enabled
269        let mut meta_dict = Dictionary::new();
270        meta_dict.set("Type", Object::Name("Metadata".to_string()));
271        assert_eq!(handler.get_stream_filter(&meta_dict), None); // Use default
272    }
273
274    #[test]
275    fn test_encrypt_embedded_file() {
276        let filter_manager = create_test_filter_manager();
277        let handler = EmbeddedFileEncryption::new(
278            Some("EmbeddedFileFilter".to_string()),
279            false,
280            filter_manager,
281        );
282
283        let data = b"Embedded file content";
284        let obj_id = ObjectId::new(1, 0);
285        let key = EncryptionKey::new(vec![0x01; 16]);
286
287        let encrypted = handler.encrypt_embedded_file(data, &obj_id, &key).unwrap();
288        assert_ne!(encrypted, data);
289    }
290
291    #[test]
292    fn test_encrypt_embedded_file_no_eff() {
293        let filter_manager = create_test_filter_manager();
294        let handler = EmbeddedFileEncryption::new(
295            None, // No EFF filter
296            false,
297            filter_manager,
298        );
299
300        let data = b"Embedded file content";
301        let obj_id = ObjectId::new(1, 0);
302        let key = EncryptionKey::new(vec![0x01; 16]);
303
304        let result = handler.encrypt_embedded_file(data, &obj_id, &key).unwrap();
305        assert_eq!(result, data); // Should return unchanged
306    }
307
308    #[test]
309    fn test_process_stream_encryption_embedded_file() {
310        let filter_manager = create_test_filter_manager();
311        let handler = EmbeddedFileEncryption::new(
312            Some("EmbeddedFileFilter".to_string()),
313            false,
314            filter_manager,
315        );
316
317        let mut dict = Dictionary::new();
318        dict.set("Type", Object::Name("EmbeddedFile".to_string()));
319
320        let data = b"Embedded file data";
321        let obj_id = ObjectId::new(1, 0);
322        let key = EncryptionKey::new(vec![0x01; 16]);
323
324        let encrypted = handler
325            .process_stream_encryption(
326                &dict, data, &obj_id, &key, true, // encrypt
327            )
328            .unwrap();
329
330        assert_ne!(encrypted, data);
331    }
332
333    #[test]
334    fn test_process_stream_encryption_metadata_no_encrypt() {
335        let filter_manager = create_test_filter_manager();
336        let handler = EmbeddedFileEncryption::new(
337            Some("EmbeddedFileFilter".to_string()),
338            false, // Don't encrypt metadata
339            filter_manager,
340        );
341
342        let mut dict = Dictionary::new();
343        dict.set("Type", Object::Name("Metadata".to_string()));
344
345        let data = b"Metadata content";
346        let obj_id = ObjectId::new(1, 0);
347        let key = EncryptionKey::new(vec![0x01; 16]);
348
349        let result = handler
350            .process_stream_encryption(
351                &dict, data, &obj_id, &key, true, // encrypt
352            )
353            .unwrap();
354
355        assert_eq!(result, data); // Should not be encrypted
356    }
357
358    #[test]
359    fn test_process_stream_encryption_regular_stream() {
360        let filter_manager = create_test_filter_manager();
361        let handler = EmbeddedFileEncryption::new(
362            Some("EmbeddedFileFilter".to_string()),
363            true,
364            filter_manager,
365        );
366
367        let dict = Dictionary::new(); // Regular stream
368        let data = b"Regular stream data";
369        let obj_id = ObjectId::new(1, 0);
370        let key = EncryptionKey::new(vec![0x01; 16]);
371
372        let encrypted = handler
373            .process_stream_encryption(
374                &dict, data, &obj_id, &key, true, // encrypt
375            )
376            .unwrap();
377
378        assert_ne!(encrypted, data); // Should be encrypted normally
379    }
380
381    #[test]
382    fn test_extended_encryption_dict() {
383        let base = crate::encryption::EncryptionDictionary::rc4_128bit(
384            vec![0u8; 32],
385            vec![1u8; 32],
386            crate::encryption::Permissions::all(),
387            None,
388        );
389
390        let extended = ExtendedEncryptionDict::new(base, Some("EmbeddedFileFilter".to_string()));
391
392        let dict = extended.to_dict();
393        assert_eq!(
394            dict.get("EFF"),
395            Some(&Object::Name("EmbeddedFileFilter".to_string()))
396        );
397    }
398
399    #[test]
400    fn test_extended_encryption_dict_no_eff() {
401        let base = crate::encryption::EncryptionDictionary::rc4_128bit(
402            vec![0u8; 32],
403            vec![1u8; 32],
404            crate::encryption::Permissions::all(),
405            None,
406        );
407
408        let extended = ExtendedEncryptionDict::new(base, None);
409        let dict = extended.to_dict();
410
411        assert!(!dict.contains_key("EFF"));
412    }
413
414    #[test]
415    fn test_decrypt_embedded_file() {
416        let filter_manager = create_test_filter_manager();
417        let handler = EmbeddedFileEncryption::new(
418            Some("EmbeddedFileFilter".to_string()),
419            false,
420            filter_manager,
421        );
422
423        let data = b"Encrypted embedded file";
424        let obj_id = ObjectId::new(1, 0);
425        let key = EncryptionKey::new(vec![0x01; 16]);
426
427        // First encrypt
428        let encrypted = handler.encrypt_embedded_file(data, &obj_id, &key).unwrap();
429
430        // Then decrypt
431        let decrypted = handler
432            .decrypt_embedded_file(&encrypted, &obj_id, &key)
433            .unwrap();
434
435        // RC4 should be reversible
436        assert_eq!(decrypted, data);
437    }
438
439    #[test]
440    fn test_process_stream_decryption() {
441        let filter_manager = create_test_filter_manager();
442        let handler = EmbeddedFileEncryption::new(
443            Some("EmbeddedFileFilter".to_string()),
444            false,
445            filter_manager,
446        );
447
448        let mut dict = Dictionary::new();
449        dict.set("Type", Object::Name("EmbeddedFile".to_string()));
450
451        let original_data = b"Original embedded file";
452        let obj_id = ObjectId::new(1, 0);
453        let key = EncryptionKey::new(vec![0x01; 16]);
454
455        // Encrypt
456        let encrypted = handler
457            .process_stream_encryption(&dict, original_data, &obj_id, &key, true)
458            .unwrap();
459
460        // Decrypt
461        let decrypted = handler
462            .process_stream_encryption(&dict, &encrypted, &obj_id, &key, false)
463            .unwrap();
464
465        assert_eq!(decrypted, original_data);
466    }
467}