Skip to main content

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(owner_hash, user_hash, permissions, None);
300
301        assert_eq!(enc_dict.filter, "Standard");
302        assert_eq!(enc_dict.v, 2);
303        assert_eq!(enc_dict.length, Some(16));
304        assert_eq!(enc_dict.r, 3);
305    }
306
307    #[test]
308    fn test_encryption_dict_to_pdf() {
309        let enc_dict =
310            EncryptionDictionary::rc4_40bit(vec![0u8; 32], vec![1u8; 32], Permissions::new(), None);
311
312        let pdf_dict = enc_dict.to_dict();
313        assert_eq!(
314            pdf_dict.get("Filter"),
315            Some(&Object::Name("Standard".to_string()))
316        );
317        assert_eq!(pdf_dict.get("V"), Some(&Object::Integer(1)));
318        assert_eq!(pdf_dict.get("Length"), Some(&Object::Integer(40))); // 5 bytes * 8 bits
319        assert_eq!(pdf_dict.get("R"), Some(&Object::Integer(2)));
320        assert!(pdf_dict.get("O").is_some());
321        assert!(pdf_dict.get("U").is_some());
322        assert!(pdf_dict.get("P").is_some());
323    }
324
325    #[test]
326    fn test_stream_filter_names() {
327        let identity = StreamFilter::Identity;
328        let std_cf = StreamFilter::StdCF;
329        let custom = StreamFilter::Custom("MyFilter".to_string());
330
331        // Test that they can be created and cloned
332        let _identity_clone = identity;
333        let _std_cf_clone = std_cf;
334        let _custom_clone = custom;
335    }
336
337    #[test]
338    fn test_string_filter_names() {
339        let identity = StringFilter::Identity;
340        let std_cf = StringFilter::StdCF;
341        let custom = StringFilter::Custom("MyStringFilter".to_string());
342
343        // Test that they can be created and cloned
344        let _identity_clone = identity;
345        let _std_cf_clone = std_cf;
346        let _custom_clone = custom;
347    }
348
349    #[test]
350    fn test_encryption_algorithm_variants() {
351        assert_eq!(EncryptionAlgorithm::RC4, EncryptionAlgorithm::RC4);
352        assert_eq!(EncryptionAlgorithm::AES128, EncryptionAlgorithm::AES128);
353        assert_eq!(EncryptionAlgorithm::AES256, EncryptionAlgorithm::AES256);
354        assert_ne!(EncryptionAlgorithm::RC4, EncryptionAlgorithm::AES128);
355
356        // Test debug format
357        let _ = format!("{:?}", EncryptionAlgorithm::RC4);
358        let _ = format!("{:?}", EncryptionAlgorithm::AES128);
359        let _ = format!("{:?}", EncryptionAlgorithm::AES256);
360    }
361
362    #[test]
363    fn test_crypt_filter_method_variants() {
364        assert_eq!(CryptFilterMethod::None, CryptFilterMethod::None);
365        assert_eq!(CryptFilterMethod::V2, CryptFilterMethod::V2);
366        assert_eq!(CryptFilterMethod::AESV2, CryptFilterMethod::AESV2);
367        assert_eq!(CryptFilterMethod::AESV3, CryptFilterMethod::AESV3);
368        assert_ne!(CryptFilterMethod::None, CryptFilterMethod::V2);
369
370        // Test debug format
371        let _ = format!("{:?}", CryptFilterMethod::None);
372        let _ = format!("{:?}", CryptFilterMethod::V2);
373        let _ = format!("{:?}", CryptFilterMethod::AESV2);
374        let _ = format!("{:?}", CryptFilterMethod::AESV3);
375    }
376
377    #[test]
378    fn test_crypt_filter_custom() {
379        let filter = CryptFilter {
380            name: "CustomFilter".to_string(),
381            method: CryptFilterMethod::AESV2,
382            length: Some(32),
383        };
384
385        let dict = filter.to_dict();
386        assert_eq!(dict.get("CFM"), Some(&Object::Name("AESV2".to_string())));
387        assert_eq!(dict.get("Length"), Some(&Object::Integer(32)));
388    }
389
390    #[test]
391    fn test_crypt_filter_no_optional_fields() {
392        let filter = CryptFilter {
393            name: "MinimalFilter".to_string(),
394            method: CryptFilterMethod::V2,
395            length: None,
396        };
397
398        let dict = filter.to_dict();
399        assert_eq!(dict.get("CFM"), Some(&Object::Name("V2".to_string())));
400        assert!(dict.get("Length").is_none());
401    }
402
403    #[test]
404    fn test_encryption_dict_with_file_id() {
405        let owner_hash = vec![0u8; 32];
406        let user_hash = vec![1u8; 32];
407        let permissions = Permissions::new();
408        let file_id = vec![42u8; 16];
409
410        let enc_dict =
411            EncryptionDictionary::rc4_40bit(owner_hash, user_hash, permissions, Some(file_id));
412
413        // The file_id is used internally but not stored as a separate field
414        assert_eq!(enc_dict.filter, "Standard");
415        assert_eq!(enc_dict.v, 1);
416    }
417
418    #[test]
419    fn test_encryption_dict_rc4_128bit_with_metadata() {
420        let owner_hash = vec![0u8; 32];
421        let user_hash = vec![1u8; 32];
422        let permissions = Permissions::all();
423
424        let enc_dict = EncryptionDictionary::rc4_128bit(owner_hash, user_hash, permissions, None);
425
426        assert_eq!(enc_dict.v, 2);
427        assert_eq!(enc_dict.length, Some(16));
428        assert_eq!(enc_dict.r, 3);
429        assert!(enc_dict.encrypt_metadata);
430    }
431
432    #[test]
433    fn test_encryption_dict_to_pdf_with_metadata_false() {
434        let mut enc_dict = EncryptionDictionary::rc4_128bit(
435            vec![0u8; 32],
436            vec![1u8; 32],
437            Permissions::new(),
438            None,
439        );
440        enc_dict.encrypt_metadata = false;
441        enc_dict.v = 4; // Ensure V >= 4 for EncryptMetadata
442
443        let pdf_dict = enc_dict.to_dict();
444        assert_eq!(
445            pdf_dict.get("EncryptMetadata"),
446            Some(&Object::Boolean(false))
447        );
448    }
449
450    #[test]
451    fn test_encryption_dict_with_crypt_filters() {
452        let mut enc_dict = EncryptionDictionary::rc4_128bit(
453            vec![0u8; 32],
454            vec![1u8; 32],
455            Permissions::new(),
456            None,
457        );
458
459        let filter = CryptFilter::standard(CryptFilterMethod::AESV2);
460        enc_dict.cf = Some(vec![filter]);
461        enc_dict.stm_f = Some(StreamFilter::StdCF);
462        enc_dict.str_f = Some(StringFilter::StdCF);
463
464        let pdf_dict = enc_dict.to_dict();
465        assert!(pdf_dict.get("CF").is_some());
466        assert_eq!(
467            pdf_dict.get("StmF"),
468            Some(&Object::Name("StdCF".to_string()))
469        );
470        assert_eq!(
471            pdf_dict.get("StrF"),
472            Some(&Object::Name("StdCF".to_string()))
473        );
474    }
475
476    #[test]
477    fn test_encryption_dict_with_identity_filters() {
478        let mut enc_dict = EncryptionDictionary::rc4_128bit(
479            vec![0u8; 32],
480            vec![1u8; 32],
481            Permissions::new(),
482            None,
483        );
484
485        enc_dict.stm_f = Some(StreamFilter::Identity);
486        enc_dict.str_f = Some(StringFilter::Identity);
487
488        let pdf_dict = enc_dict.to_dict();
489        assert_eq!(
490            pdf_dict.get("StmF"),
491            Some(&Object::Name("Identity".to_string()))
492        );
493        assert_eq!(
494            pdf_dict.get("StrF"),
495            Some(&Object::Name("Identity".to_string()))
496        );
497    }
498
499    #[test]
500    fn test_encryption_dict_with_custom_filters() {
501        let mut enc_dict = EncryptionDictionary::rc4_128bit(
502            vec![0u8; 32],
503            vec![1u8; 32],
504            Permissions::new(),
505            None,
506        );
507
508        enc_dict.stm_f = Some(StreamFilter::Custom("MyStreamFilter".to_string()));
509        enc_dict.str_f = Some(StringFilter::Custom("MyStringFilter".to_string()));
510
511        let pdf_dict = enc_dict.to_dict();
512        assert_eq!(
513            pdf_dict.get("StmF"),
514            Some(&Object::Name("MyStreamFilter".to_string()))
515        );
516        assert_eq!(
517            pdf_dict.get("StrF"),
518            Some(&Object::Name("MyStringFilter".to_string()))
519        );
520    }
521
522    #[test]
523    fn test_multiple_crypt_filters() {
524        let mut enc_dict = EncryptionDictionary::rc4_128bit(
525            vec![0u8; 32],
526            vec![1u8; 32],
527            Permissions::new(),
528            None,
529        );
530
531        let filter1 = CryptFilter::standard(CryptFilterMethod::V2);
532        let filter2 = CryptFilter {
533            name: "AESFilter".to_string(),
534            method: CryptFilterMethod::AESV2,
535            length: Some(16),
536        };
537
538        enc_dict.cf = Some(vec![filter1, filter2]);
539
540        let pdf_dict = enc_dict.to_dict();
541        if let Some(Object::Dictionary(cf_dict)) = pdf_dict.get("CF") {
542            assert!(cf_dict.get("StdCF").is_some());
543            assert!(cf_dict.get("AESFilter").is_some());
544        } else {
545            panic!("CF should be a dictionary");
546        }
547    }
548}