oxidize_pdf/encryption/
encryption_dict.rs

1//! PDF encryption dictionary structures
2
3use crate::encryption::Permissions;
4use crate::objects::{Dictionary, Object};
5
6/// Encryption algorithm
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub enum EncryptionAlgorithm {
9    /// RC4 encryption
10    RC4,
11    /// AES encryption (128-bit)
12    AES128,
13    /// AES encryption (256-bit)
14    AES256,
15}
16
17/// Crypt filter method
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum CryptFilterMethod {
20    /// No encryption
21    None,
22    /// RC4 encryption
23    V2,
24    /// AES encryption
25    AESV2,
26    /// AES-256 encryption
27    AESV3,
28}
29
30impl CryptFilterMethod {
31    /// Get PDF name
32    pub fn pdf_name(&self) -> &'static str {
33        match self {
34            CryptFilterMethod::None => "None",
35            CryptFilterMethod::V2 => "V2",
36            CryptFilterMethod::AESV2 => "AESV2",
37            CryptFilterMethod::AESV3 => "AESV3",
38        }
39    }
40}
41
42/// Stream filter name
43#[derive(Debug, Clone)]
44pub enum StreamFilter {
45    /// Identity (no encryption)
46    Identity,
47    /// Standard encryption
48    StdCF,
49    /// Custom filter
50    Custom(String),
51}
52
53/// String filter name
54#[derive(Debug, Clone)]
55pub enum StringFilter {
56    /// Identity (no encryption)
57    Identity,
58    /// Standard encryption
59    StdCF,
60    /// Custom filter
61    Custom(String),
62}
63
64/// Crypt filter definition
65#[derive(Debug, Clone)]
66pub struct CryptFilter {
67    /// Filter name
68    pub name: String,
69    /// Encryption method
70    pub method: CryptFilterMethod,
71    /// Length in bytes (for RC4)
72    pub length: Option<u32>,
73}
74
75impl CryptFilter {
76    /// Create standard crypt filter
77    pub fn standard(method: CryptFilterMethod) -> Self {
78        Self {
79            name: "StdCF".to_string(),
80            method,
81            length: match method {
82                CryptFilterMethod::V2 => Some(16), // 128-bit
83                _ => None,
84            },
85        }
86    }
87
88    /// Convert to dictionary
89    pub fn to_dict(&self) -> Dictionary {
90        let mut dict = Dictionary::new();
91
92        dict.set("CFM", Object::Name(self.method.pdf_name().to_string()));
93
94        if let Some(length) = self.length {
95            dict.set("Length", Object::Integer(length as i64));
96        }
97
98        dict
99    }
100}
101
102/// PDF encryption dictionary
103#[derive(Debug, Clone)]
104pub struct EncryptionDictionary {
105    /// Filter (always "Standard" for standard security handler)
106    pub filter: String,
107    /// Sub-filter (for public-key security handlers)
108    pub sub_filter: Option<String>,
109    /// Algorithm version (1-5)
110    pub v: u32,
111    /// Key length in bytes
112    pub length: Option<u32>,
113    /// Crypt filters
114    pub cf: Option<Vec<CryptFilter>>,
115    /// Stream filter
116    pub stm_f: Option<StreamFilter>,
117    /// String filter
118    pub str_f: Option<StringFilter>,
119    /// Identity filter
120    pub ef: Option<String>,
121    /// Revision number
122    pub r: u32,
123    /// Owner password hash (32 bytes)
124    pub o: Vec<u8>,
125    /// User password hash (32 bytes)
126    pub u: Vec<u8>,
127    /// Permissions
128    pub p: Permissions,
129    /// Whether to encrypt metadata
130    pub encrypt_metadata: bool,
131    /// Document ID (first element)
132    pub id: Option<Vec<u8>>,
133}
134
135impl EncryptionDictionary {
136    /// Create RC4 40-bit encryption dictionary
137    pub fn rc4_40bit(
138        owner_hash: Vec<u8>,
139        user_hash: Vec<u8>,
140        permissions: Permissions,
141        id: Option<Vec<u8>>,
142    ) -> Self {
143        Self {
144            filter: "Standard".to_string(),
145            sub_filter: None,
146            v: 1,
147            length: Some(5), // 40 bits = 5 bytes
148            cf: None,
149            stm_f: None,
150            str_f: None,
151            ef: None,
152            r: 2,
153            o: owner_hash,
154            u: user_hash,
155            p: permissions,
156            encrypt_metadata: true,
157            id,
158        }
159    }
160
161    /// Create RC4 128-bit encryption dictionary
162    pub fn rc4_128bit(
163        owner_hash: Vec<u8>,
164        user_hash: Vec<u8>,
165        permissions: Permissions,
166        id: Option<Vec<u8>>,
167    ) -> Self {
168        Self {
169            filter: "Standard".to_string(),
170            sub_filter: None,
171            v: 2,
172            length: Some(16), // 128 bits = 16 bytes
173            cf: None,
174            stm_f: None,
175            str_f: None,
176            ef: None,
177            r: 3,
178            o: owner_hash,
179            u: user_hash,
180            p: permissions,
181            encrypt_metadata: true,
182            id,
183        }
184    }
185
186    /// Convert to PDF dictionary
187    pub fn to_dict(&self) -> Dictionary {
188        let mut dict = Dictionary::new();
189
190        dict.set("Filter", Object::Name(self.filter.clone()));
191
192        if let Some(ref sub_filter) = self.sub_filter {
193            dict.set("SubFilter", Object::Name(sub_filter.clone()));
194        }
195
196        dict.set("V", Object::Integer(self.v as i64));
197
198        if let Some(length) = self.length {
199            dict.set("Length", Object::Integer((length * 8) as i64)); // Convert bytes to bits
200        }
201
202        dict.set("R", Object::Integer(self.r as i64));
203        dict.set(
204            "O",
205            Object::String(String::from_utf8_lossy(&self.o).to_string()),
206        );
207        dict.set(
208            "U",
209            Object::String(String::from_utf8_lossy(&self.u).to_string()),
210        );
211        dict.set("P", Object::Integer(self.p.bits() as i32 as i64));
212
213        if !self.encrypt_metadata && self.v >= 4 {
214            dict.set("EncryptMetadata", Object::Boolean(false));
215        }
216
217        // Add crypt filters if present
218        if let Some(ref cf_list) = self.cf {
219            let mut cf_dict = Dictionary::new();
220            for filter in cf_list {
221                cf_dict.set(&filter.name, Object::Dictionary(filter.to_dict()));
222            }
223            dict.set("CF", Object::Dictionary(cf_dict));
224        }
225
226        // Add stream filter
227        if let Some(ref stm_f) = self.stm_f {
228            match stm_f {
229                StreamFilter::Identity => dict.set("StmF", Object::Name("Identity".to_string())),
230                StreamFilter::StdCF => dict.set("StmF", Object::Name("StdCF".to_string())),
231                StreamFilter::Custom(name) => dict.set("StmF", Object::Name(name.clone())),
232            }
233        }
234
235        // Add string filter
236        if let Some(ref str_f) = self.str_f {
237            match str_f {
238                StringFilter::Identity => dict.set("StrF", Object::Name("Identity".to_string())),
239                StringFilter::StdCF => dict.set("StrF", Object::Name("StdCF".to_string())),
240                StringFilter::Custom(name) => dict.set("StrF", Object::Name(name.clone())),
241            }
242        }
243
244        dict
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_crypt_filter_method() {
254        assert_eq!(CryptFilterMethod::None.pdf_name(), "None");
255        assert_eq!(CryptFilterMethod::V2.pdf_name(), "V2");
256        assert_eq!(CryptFilterMethod::AESV2.pdf_name(), "AESV2");
257        assert_eq!(CryptFilterMethod::AESV3.pdf_name(), "AESV3");
258    }
259
260    #[test]
261    fn test_crypt_filter() {
262        let filter = CryptFilter::standard(CryptFilterMethod::V2);
263        assert_eq!(filter.name, "StdCF");
264        assert_eq!(filter.method, CryptFilterMethod::V2);
265        assert_eq!(filter.length, Some(16));
266
267        let dict = filter.to_dict();
268        assert_eq!(dict.get("CFM"), Some(&Object::Name("V2".to_string())));
269        assert_eq!(dict.get("Length"), Some(&Object::Integer(16)));
270    }
271
272    #[test]
273    fn test_rc4_40bit_encryption_dict() {
274        let owner_hash = vec![0u8; 32];
275        let user_hash = vec![1u8; 32];
276        let permissions = Permissions::new();
277
278        let enc_dict = EncryptionDictionary::rc4_40bit(
279            owner_hash.clone(),
280            user_hash.clone(),
281            permissions,
282            None,
283        );
284
285        assert_eq!(enc_dict.filter, "Standard");
286        assert_eq!(enc_dict.v, 1);
287        assert_eq!(enc_dict.length, Some(5));
288        assert_eq!(enc_dict.r, 2);
289        assert_eq!(enc_dict.o, owner_hash);
290        assert_eq!(enc_dict.u, user_hash);
291    }
292
293    #[test]
294    fn test_rc4_128bit_encryption_dict() {
295        let owner_hash = vec![0u8; 32];
296        let user_hash = vec![1u8; 32];
297        let permissions = Permissions::all();
298
299        let enc_dict = EncryptionDictionary::rc4_128bit(
300            owner_hash.clone(),
301            user_hash.clone(),
302            permissions,
303            None,
304        );
305
306        assert_eq!(enc_dict.filter, "Standard");
307        assert_eq!(enc_dict.v, 2);
308        assert_eq!(enc_dict.length, Some(16));
309        assert_eq!(enc_dict.r, 3);
310    }
311
312    #[test]
313    fn test_encryption_dict_to_pdf() {
314        let enc_dict =
315            EncryptionDictionary::rc4_40bit(vec![0u8; 32], vec![1u8; 32], Permissions::new(), None);
316
317        let pdf_dict = enc_dict.to_dict();
318        assert_eq!(
319            pdf_dict.get("Filter"),
320            Some(&Object::Name("Standard".to_string()))
321        );
322        assert_eq!(pdf_dict.get("V"), Some(&Object::Integer(1)));
323        assert_eq!(pdf_dict.get("Length"), Some(&Object::Integer(40))); // 5 bytes * 8 bits
324        assert_eq!(pdf_dict.get("R"), Some(&Object::Integer(2)));
325        assert!(pdf_dict.get("O").is_some());
326        assert!(pdf_dict.get("U").is_some());
327        assert!(pdf_dict.get("P").is_some());
328    }
329
330    #[test]
331    fn test_stream_filter_names() {
332        let identity = StreamFilter::Identity;
333        let std_cf = StreamFilter::StdCF;
334        let custom = StreamFilter::Custom("MyFilter".to_string());
335
336        // Test that they can be created and cloned
337        let _identity_clone = identity.clone();
338        let _std_cf_clone = std_cf.clone();
339        let _custom_clone = custom.clone();
340    }
341
342    #[test]
343    fn test_string_filter_names() {
344        let identity = StringFilter::Identity;
345        let std_cf = StringFilter::StdCF;
346        let custom = StringFilter::Custom("MyStringFilter".to_string());
347
348        // Test that they can be created and cloned
349        let _identity_clone = identity.clone();
350        let _std_cf_clone = std_cf.clone();
351        let _custom_clone = custom.clone();
352    }
353
354    #[test]
355    fn test_encryption_algorithm_variants() {
356        assert_eq!(EncryptionAlgorithm::RC4, EncryptionAlgorithm::RC4);
357        assert_eq!(EncryptionAlgorithm::AES128, EncryptionAlgorithm::AES128);
358        assert_eq!(EncryptionAlgorithm::AES256, EncryptionAlgorithm::AES256);
359        assert_ne!(EncryptionAlgorithm::RC4, EncryptionAlgorithm::AES128);
360
361        // Test debug format
362        let _ = format!("{:?}", EncryptionAlgorithm::RC4);
363        let _ = format!("{:?}", EncryptionAlgorithm::AES128);
364        let _ = format!("{:?}", EncryptionAlgorithm::AES256);
365    }
366
367    #[test]
368    fn test_crypt_filter_method_variants() {
369        assert_eq!(CryptFilterMethod::None, CryptFilterMethod::None);
370        assert_eq!(CryptFilterMethod::V2, CryptFilterMethod::V2);
371        assert_eq!(CryptFilterMethod::AESV2, CryptFilterMethod::AESV2);
372        assert_eq!(CryptFilterMethod::AESV3, CryptFilterMethod::AESV3);
373        assert_ne!(CryptFilterMethod::None, CryptFilterMethod::V2);
374
375        // Test debug format
376        let _ = format!("{:?}", CryptFilterMethod::None);
377        let _ = format!("{:?}", CryptFilterMethod::V2);
378        let _ = format!("{:?}", CryptFilterMethod::AESV2);
379        let _ = format!("{:?}", CryptFilterMethod::AESV3);
380    }
381
382    #[test]
383    fn test_crypt_filter_custom() {
384        let filter = CryptFilter {
385            name: "CustomFilter".to_string(),
386            method: CryptFilterMethod::AESV2,
387            length: Some(32),
388        };
389
390        let dict = filter.to_dict();
391        assert_eq!(dict.get("CFM"), Some(&Object::Name("AESV2".to_string())));
392        assert_eq!(dict.get("Length"), Some(&Object::Integer(32)));
393    }
394
395    #[test]
396    fn test_crypt_filter_no_optional_fields() {
397        let filter = CryptFilter {
398            name: "MinimalFilter".to_string(),
399            method: CryptFilterMethod::V2,
400            length: None,
401        };
402
403        let dict = filter.to_dict();
404        assert_eq!(dict.get("CFM"), Some(&Object::Name("V2".to_string())));
405        assert!(dict.get("Length").is_none());
406    }
407
408    #[test]
409    fn test_encryption_dict_with_file_id() {
410        let owner_hash = vec![0u8; 32];
411        let user_hash = vec![1u8; 32];
412        let permissions = Permissions::new();
413        let file_id = vec![42u8; 16];
414
415        let enc_dict = EncryptionDictionary::rc4_40bit(
416            owner_hash.clone(),
417            user_hash.clone(),
418            permissions,
419            Some(file_id.clone()),
420        );
421
422        // The file_id is used internally but not stored as a separate field
423        assert_eq!(enc_dict.filter, "Standard");
424        assert_eq!(enc_dict.v, 1);
425    }
426
427    #[test]
428    fn test_encryption_dict_rc4_128bit_with_metadata() {
429        let owner_hash = vec![0u8; 32];
430        let user_hash = vec![1u8; 32];
431        let permissions = Permissions::all();
432
433        let enc_dict = EncryptionDictionary::rc4_128bit(
434            owner_hash.clone(),
435            user_hash.clone(),
436            permissions,
437            None,
438        );
439
440        assert_eq!(enc_dict.v, 2);
441        assert_eq!(enc_dict.length, Some(16));
442        assert_eq!(enc_dict.r, 3);
443        assert!(enc_dict.encrypt_metadata);
444    }
445
446    #[test]
447    fn test_encryption_dict_to_pdf_with_metadata_false() {
448        let mut enc_dict = EncryptionDictionary::rc4_128bit(
449            vec![0u8; 32],
450            vec![1u8; 32],
451            Permissions::new(),
452            None,
453        );
454        enc_dict.encrypt_metadata = false;
455        enc_dict.v = 4; // Ensure V >= 4 for EncryptMetadata
456
457        let pdf_dict = enc_dict.to_dict();
458        assert_eq!(
459            pdf_dict.get("EncryptMetadata"),
460            Some(&Object::Boolean(false))
461        );
462    }
463
464    #[test]
465    fn test_encryption_dict_with_crypt_filters() {
466        let mut enc_dict = EncryptionDictionary::rc4_128bit(
467            vec![0u8; 32],
468            vec![1u8; 32],
469            Permissions::new(),
470            None,
471        );
472
473        let filter = CryptFilter::standard(CryptFilterMethod::AESV2);
474        enc_dict.cf = Some(vec![filter]);
475        enc_dict.stm_f = Some(StreamFilter::StdCF);
476        enc_dict.str_f = Some(StringFilter::StdCF);
477
478        let pdf_dict = enc_dict.to_dict();
479        assert!(pdf_dict.get("CF").is_some());
480        assert_eq!(
481            pdf_dict.get("StmF"),
482            Some(&Object::Name("StdCF".to_string()))
483        );
484        assert_eq!(
485            pdf_dict.get("StrF"),
486            Some(&Object::Name("StdCF".to_string()))
487        );
488    }
489
490    #[test]
491    fn test_encryption_dict_with_identity_filters() {
492        let mut enc_dict = EncryptionDictionary::rc4_128bit(
493            vec![0u8; 32],
494            vec![1u8; 32],
495            Permissions::new(),
496            None,
497        );
498
499        enc_dict.stm_f = Some(StreamFilter::Identity);
500        enc_dict.str_f = Some(StringFilter::Identity);
501
502        let pdf_dict = enc_dict.to_dict();
503        assert_eq!(
504            pdf_dict.get("StmF"),
505            Some(&Object::Name("Identity".to_string()))
506        );
507        assert_eq!(
508            pdf_dict.get("StrF"),
509            Some(&Object::Name("Identity".to_string()))
510        );
511    }
512
513    #[test]
514    fn test_encryption_dict_with_custom_filters() {
515        let mut enc_dict = EncryptionDictionary::rc4_128bit(
516            vec![0u8; 32],
517            vec![1u8; 32],
518            Permissions::new(),
519            None,
520        );
521
522        enc_dict.stm_f = Some(StreamFilter::Custom("MyStreamFilter".to_string()));
523        enc_dict.str_f = Some(StringFilter::Custom("MyStringFilter".to_string()));
524
525        let pdf_dict = enc_dict.to_dict();
526        assert_eq!(
527            pdf_dict.get("StmF"),
528            Some(&Object::Name("MyStreamFilter".to_string()))
529        );
530        assert_eq!(
531            pdf_dict.get("StrF"),
532            Some(&Object::Name("MyStringFilter".to_string()))
533        );
534    }
535
536    #[test]
537    fn test_multiple_crypt_filters() {
538        let mut enc_dict = EncryptionDictionary::rc4_128bit(
539            vec![0u8; 32],
540            vec![1u8; 32],
541            Permissions::new(),
542            None,
543        );
544
545        let filter1 = CryptFilter::standard(CryptFilterMethod::V2);
546        let filter2 = CryptFilter {
547            name: "AESFilter".to_string(),
548            method: CryptFilterMethod::AESV2,
549            length: Some(16),
550        };
551
552        enc_dict.cf = Some(vec![filter1, filter2]);
553
554        let pdf_dict = enc_dict.to_dict();
555        if let Some(Object::Dictionary(cf_dict)) = pdf_dict.get("CF") {
556            assert!(cf_dict.get("StdCF").is_some());
557            assert!(cf_dict.get("AESFilter").is_some());
558        } else {
559            panic!("CF should be a dictionary");
560        }
561    }
562}