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    /// UE entry: encrypted file encryption key (user password, R5/R6 only)
134    pub ue: Option<Vec<u8>>,
135    /// OE entry: encrypted file encryption key (owner password, R5/R6 only)
136    pub oe: Option<Vec<u8>>,
137    /// Perms entry: encrypted permissions verification (R6 only)
138    pub perms: Option<Vec<u8>>,
139}
140
141impl EncryptionDictionary {
142    /// Create RC4 40-bit encryption dictionary
143    pub fn rc4_40bit(
144        owner_hash: Vec<u8>,
145        user_hash: Vec<u8>,
146        permissions: Permissions,
147        id: Option<Vec<u8>>,
148    ) -> Self {
149        Self {
150            filter: "Standard".to_string(),
151            sub_filter: None,
152            v: 1,
153            length: Some(5), // 40 bits = 5 bytes
154            cf: None,
155            stm_f: None,
156            str_f: None,
157            ef: None,
158            r: 2,
159            o: owner_hash,
160            u: user_hash,
161            p: permissions,
162            encrypt_metadata: true,
163            id,
164            ue: None,
165            oe: None,
166            perms: None,
167        }
168    }
169
170    /// Create RC4 128-bit encryption dictionary
171    pub fn rc4_128bit(
172        owner_hash: Vec<u8>,
173        user_hash: Vec<u8>,
174        permissions: Permissions,
175        id: Option<Vec<u8>>,
176    ) -> Self {
177        Self {
178            filter: "Standard".to_string(),
179            sub_filter: None,
180            v: 2,
181            length: Some(16), // 128 bits = 16 bytes
182            cf: None,
183            stm_f: None,
184            str_f: None,
185            ef: None,
186            r: 3,
187            o: owner_hash,
188            u: user_hash,
189            p: permissions,
190            encrypt_metadata: true,
191            id,
192            ue: None,
193            oe: None,
194            perms: None,
195        }
196    }
197
198    /// Create AES-128 encryption dictionary (V=4, R=4, AESV2 crypt filter)
199    ///
200    /// Per ISO 32000-1 §7.6.1 Table 20: V=4 uses crypt filters to specify
201    /// the encryption method per stream/string. R=4 is used for AES-128.
202    pub fn aes_128(
203        owner_hash: Vec<u8>,
204        user_hash: Vec<u8>,
205        permissions: Permissions,
206        id: Option<Vec<u8>>,
207    ) -> Self {
208        Self {
209            filter: "Standard".to_string(),
210            sub_filter: None,
211            v: 4,
212            length: Some(16), // 128 bits = 16 bytes
213            cf: Some(vec![CryptFilter::standard(CryptFilterMethod::AESV2)]),
214            stm_f: Some(StreamFilter::StdCF),
215            str_f: Some(StringFilter::StdCF),
216            ef: None,
217            r: 4,
218            o: owner_hash,
219            u: user_hash,
220            p: permissions,
221            encrypt_metadata: true,
222            id,
223            ue: None,
224            oe: None,
225            perms: None,
226        }
227    }
228
229    /// Create AES-256 encryption dictionary (V=5, R=5, AESV3 crypt filter)
230    ///
231    /// Per ISO 32000-2 §7.6.1: V=5 uses 256-bit AES encryption with
232    /// crypt filters. R=5 uses the original AES-256 key derivation.
233    pub fn aes_256(
234        owner_hash: Vec<u8>,
235        user_hash: Vec<u8>,
236        permissions: Permissions,
237        id: Option<Vec<u8>>,
238    ) -> Self {
239        Self {
240            filter: "Standard".to_string(),
241            sub_filter: None,
242            v: 5,
243            length: Some(32), // 256 bits = 32 bytes
244            cf: Some(vec![CryptFilter::standard(CryptFilterMethod::AESV3)]),
245            stm_f: Some(StreamFilter::StdCF),
246            str_f: Some(StringFilter::StdCF),
247            ef: None,
248            r: 5,
249            o: owner_hash,
250            u: user_hash,
251            p: permissions,
252            encrypt_metadata: true,
253            id,
254            ue: None,
255            oe: None,
256            perms: None,
257        }
258    }
259
260    /// Set R5/R6 additional entries (UE, OE) on the encryption dictionary.
261    pub fn with_r5_entries(mut self, ue: Vec<u8>, oe: Vec<u8>) -> Self {
262        self.ue = Some(ue);
263        self.oe = Some(oe);
264        self
265    }
266
267    /// Convert to PDF dictionary
268    pub fn to_dict(&self) -> Dictionary {
269        let mut dict = Dictionary::new();
270
271        dict.set("Filter", Object::Name(self.filter.clone()));
272
273        if let Some(ref sub_filter) = self.sub_filter {
274            dict.set("SubFilter", Object::Name(sub_filter.clone()));
275        }
276
277        dict.set("V", Object::Integer(self.v as i64));
278
279        if let Some(length) = self.length {
280            dict.set("Length", Object::Integer((length * 8) as i64)); // Convert bytes to bits
281        }
282
283        dict.set("R", Object::Integer(self.r as i64));
284        dict.set("O", Object::ByteString(self.o.clone()));
285        dict.set("U", Object::ByteString(self.u.clone()));
286        dict.set("P", Object::Integer(self.p.bits() as i32 as i64));
287
288        if !self.encrypt_metadata && self.v >= 4 {
289            dict.set("EncryptMetadata", Object::Boolean(false));
290        }
291
292        // Add crypt filters if present
293        if let Some(ref cf_list) = self.cf {
294            let mut cf_dict = Dictionary::new();
295            for filter in cf_list {
296                cf_dict.set(&filter.name, Object::Dictionary(filter.to_dict()));
297            }
298            dict.set("CF", Object::Dictionary(cf_dict));
299        }
300
301        // Add stream filter
302        if let Some(ref stm_f) = self.stm_f {
303            match stm_f {
304                StreamFilter::Identity => dict.set("StmF", Object::Name("Identity".to_string())),
305                StreamFilter::StdCF => dict.set("StmF", Object::Name("StdCF".to_string())),
306                StreamFilter::Custom(name) => dict.set("StmF", Object::Name(name.clone())),
307            }
308        }
309
310        // Add string filter
311        if let Some(ref str_f) = self.str_f {
312            match str_f {
313                StringFilter::Identity => dict.set("StrF", Object::Name("Identity".to_string())),
314                StringFilter::StdCF => dict.set("StrF", Object::Name("StdCF".to_string())),
315                StringFilter::Custom(name) => dict.set("StrF", Object::Name(name.clone())),
316            }
317        }
318
319        // Add R5/R6 entries
320        if let Some(ref ue) = self.ue {
321            dict.set("UE", Object::ByteString(ue.clone()));
322        }
323        if let Some(ref oe) = self.oe {
324            dict.set("OE", Object::ByteString(oe.clone()));
325        }
326        if let Some(ref perms) = self.perms {
327            dict.set(
328                "Perms",
329                Object::String(String::from_utf8_lossy(perms).to_string()),
330            );
331        }
332
333        dict
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn test_crypt_filter_method() {
343        assert_eq!(CryptFilterMethod::None.pdf_name(), "None");
344        assert_eq!(CryptFilterMethod::V2.pdf_name(), "V2");
345        assert_eq!(CryptFilterMethod::AESV2.pdf_name(), "AESV2");
346        assert_eq!(CryptFilterMethod::AESV3.pdf_name(), "AESV3");
347    }
348
349    #[test]
350    fn test_crypt_filter() {
351        let filter = CryptFilter::standard(CryptFilterMethod::V2);
352        assert_eq!(filter.name, "StdCF");
353        assert_eq!(filter.method, CryptFilterMethod::V2);
354        assert_eq!(filter.length, Some(16));
355
356        let dict = filter.to_dict();
357        assert_eq!(dict.get("CFM"), Some(&Object::Name("V2".to_string())));
358        assert_eq!(dict.get("Length"), Some(&Object::Integer(16)));
359    }
360
361    #[test]
362    fn test_rc4_40bit_encryption_dict() {
363        let owner_hash = vec![0u8; 32];
364        let user_hash = vec![1u8; 32];
365        let permissions = Permissions::new();
366
367        let enc_dict = EncryptionDictionary::rc4_40bit(
368            owner_hash.clone(),
369            user_hash.clone(),
370            permissions,
371            None,
372        );
373
374        assert_eq!(enc_dict.filter, "Standard");
375        assert_eq!(enc_dict.v, 1);
376        assert_eq!(enc_dict.length, Some(5));
377        assert_eq!(enc_dict.r, 2);
378        assert_eq!(enc_dict.o, owner_hash);
379        assert_eq!(enc_dict.u, user_hash);
380    }
381
382    #[test]
383    fn test_rc4_128bit_encryption_dict() {
384        let owner_hash = vec![0u8; 32];
385        let user_hash = vec![1u8; 32];
386        let permissions = Permissions::all();
387
388        let enc_dict = EncryptionDictionary::rc4_128bit(owner_hash, user_hash, permissions, None);
389
390        assert_eq!(enc_dict.filter, "Standard");
391        assert_eq!(enc_dict.v, 2);
392        assert_eq!(enc_dict.length, Some(16));
393        assert_eq!(enc_dict.r, 3);
394    }
395
396    #[test]
397    fn test_encryption_dict_to_pdf() {
398        let enc_dict =
399            EncryptionDictionary::rc4_40bit(vec![0u8; 32], vec![1u8; 32], Permissions::new(), None);
400
401        let pdf_dict = enc_dict.to_dict();
402        assert_eq!(
403            pdf_dict.get("Filter"),
404            Some(&Object::Name("Standard".to_string()))
405        );
406        assert_eq!(pdf_dict.get("V"), Some(&Object::Integer(1)));
407        assert_eq!(pdf_dict.get("Length"), Some(&Object::Integer(40))); // 5 bytes * 8 bits
408        assert_eq!(pdf_dict.get("R"), Some(&Object::Integer(2)));
409        assert!(pdf_dict.get("O").is_some());
410        assert!(pdf_dict.get("U").is_some());
411        assert!(pdf_dict.get("P").is_some());
412    }
413
414    #[test]
415    fn test_stream_filter_names() {
416        let identity = StreamFilter::Identity;
417        let std_cf = StreamFilter::StdCF;
418        let custom = StreamFilter::Custom("MyFilter".to_string());
419
420        // Test that they can be created and cloned
421        let _identity_clone = identity;
422        let _std_cf_clone = std_cf;
423        let _custom_clone = custom;
424    }
425
426    #[test]
427    fn test_string_filter_names() {
428        let identity = StringFilter::Identity;
429        let std_cf = StringFilter::StdCF;
430        let custom = StringFilter::Custom("MyStringFilter".to_string());
431
432        // Test that they can be created and cloned
433        let _identity_clone = identity;
434        let _std_cf_clone = std_cf;
435        let _custom_clone = custom;
436    }
437
438    #[test]
439    fn test_encryption_algorithm_variants() {
440        assert_eq!(EncryptionAlgorithm::RC4, EncryptionAlgorithm::RC4);
441        assert_eq!(EncryptionAlgorithm::AES128, EncryptionAlgorithm::AES128);
442        assert_eq!(EncryptionAlgorithm::AES256, EncryptionAlgorithm::AES256);
443        assert_ne!(EncryptionAlgorithm::RC4, EncryptionAlgorithm::AES128);
444
445        // Test debug format
446        let _ = format!("{:?}", EncryptionAlgorithm::RC4);
447        let _ = format!("{:?}", EncryptionAlgorithm::AES128);
448        let _ = format!("{:?}", EncryptionAlgorithm::AES256);
449    }
450
451    #[test]
452    fn test_crypt_filter_method_variants() {
453        assert_eq!(CryptFilterMethod::None, CryptFilterMethod::None);
454        assert_eq!(CryptFilterMethod::V2, CryptFilterMethod::V2);
455        assert_eq!(CryptFilterMethod::AESV2, CryptFilterMethod::AESV2);
456        assert_eq!(CryptFilterMethod::AESV3, CryptFilterMethod::AESV3);
457        assert_ne!(CryptFilterMethod::None, CryptFilterMethod::V2);
458
459        // Test debug format
460        let _ = format!("{:?}", CryptFilterMethod::None);
461        let _ = format!("{:?}", CryptFilterMethod::V2);
462        let _ = format!("{:?}", CryptFilterMethod::AESV2);
463        let _ = format!("{:?}", CryptFilterMethod::AESV3);
464    }
465
466    #[test]
467    fn test_crypt_filter_custom() {
468        let filter = CryptFilter {
469            name: "CustomFilter".to_string(),
470            method: CryptFilterMethod::AESV2,
471            length: Some(32),
472        };
473
474        let dict = filter.to_dict();
475        assert_eq!(dict.get("CFM"), Some(&Object::Name("AESV2".to_string())));
476        assert_eq!(dict.get("Length"), Some(&Object::Integer(32)));
477    }
478
479    #[test]
480    fn test_crypt_filter_no_optional_fields() {
481        let filter = CryptFilter {
482            name: "MinimalFilter".to_string(),
483            method: CryptFilterMethod::V2,
484            length: None,
485        };
486
487        let dict = filter.to_dict();
488        assert_eq!(dict.get("CFM"), Some(&Object::Name("V2".to_string())));
489        assert!(dict.get("Length").is_none());
490    }
491
492    #[test]
493    fn test_encryption_dict_with_file_id() {
494        let owner_hash = vec![0u8; 32];
495        let user_hash = vec![1u8; 32];
496        let permissions = Permissions::new();
497        let file_id = vec![42u8; 16];
498
499        let enc_dict =
500            EncryptionDictionary::rc4_40bit(owner_hash, user_hash, permissions, Some(file_id));
501
502        // The file_id is used internally but not stored as a separate field
503        assert_eq!(enc_dict.filter, "Standard");
504        assert_eq!(enc_dict.v, 1);
505    }
506
507    #[test]
508    fn test_encryption_dict_rc4_128bit_with_metadata() {
509        let owner_hash = vec![0u8; 32];
510        let user_hash = vec![1u8; 32];
511        let permissions = Permissions::all();
512
513        let enc_dict = EncryptionDictionary::rc4_128bit(owner_hash, user_hash, permissions, None);
514
515        assert_eq!(enc_dict.v, 2);
516        assert_eq!(enc_dict.length, Some(16));
517        assert_eq!(enc_dict.r, 3);
518        assert!(enc_dict.encrypt_metadata);
519    }
520
521    #[test]
522    fn test_encryption_dict_to_pdf_with_metadata_false() {
523        let mut enc_dict = EncryptionDictionary::rc4_128bit(
524            vec![0u8; 32],
525            vec![1u8; 32],
526            Permissions::new(),
527            None,
528        );
529        enc_dict.encrypt_metadata = false;
530        enc_dict.v = 4; // Ensure V >= 4 for EncryptMetadata
531
532        let pdf_dict = enc_dict.to_dict();
533        assert_eq!(
534            pdf_dict.get("EncryptMetadata"),
535            Some(&Object::Boolean(false))
536        );
537    }
538
539    #[test]
540    fn test_encryption_dict_with_crypt_filters() {
541        let mut enc_dict = EncryptionDictionary::rc4_128bit(
542            vec![0u8; 32],
543            vec![1u8; 32],
544            Permissions::new(),
545            None,
546        );
547
548        let filter = CryptFilter::standard(CryptFilterMethod::AESV2);
549        enc_dict.cf = Some(vec![filter]);
550        enc_dict.stm_f = Some(StreamFilter::StdCF);
551        enc_dict.str_f = Some(StringFilter::StdCF);
552
553        let pdf_dict = enc_dict.to_dict();
554        assert!(pdf_dict.get("CF").is_some());
555        assert_eq!(
556            pdf_dict.get("StmF"),
557            Some(&Object::Name("StdCF".to_string()))
558        );
559        assert_eq!(
560            pdf_dict.get("StrF"),
561            Some(&Object::Name("StdCF".to_string()))
562        );
563    }
564
565    #[test]
566    fn test_encryption_dict_with_identity_filters() {
567        let mut enc_dict = EncryptionDictionary::rc4_128bit(
568            vec![0u8; 32],
569            vec![1u8; 32],
570            Permissions::new(),
571            None,
572        );
573
574        enc_dict.stm_f = Some(StreamFilter::Identity);
575        enc_dict.str_f = Some(StringFilter::Identity);
576
577        let pdf_dict = enc_dict.to_dict();
578        assert_eq!(
579            pdf_dict.get("StmF"),
580            Some(&Object::Name("Identity".to_string()))
581        );
582        assert_eq!(
583            pdf_dict.get("StrF"),
584            Some(&Object::Name("Identity".to_string()))
585        );
586    }
587
588    #[test]
589    fn test_encryption_dict_with_custom_filters() {
590        let mut enc_dict = EncryptionDictionary::rc4_128bit(
591            vec![0u8; 32],
592            vec![1u8; 32],
593            Permissions::new(),
594            None,
595        );
596
597        enc_dict.stm_f = Some(StreamFilter::Custom("MyStreamFilter".to_string()));
598        enc_dict.str_f = Some(StringFilter::Custom("MyStringFilter".to_string()));
599
600        let pdf_dict = enc_dict.to_dict();
601        assert_eq!(
602            pdf_dict.get("StmF"),
603            Some(&Object::Name("MyStreamFilter".to_string()))
604        );
605        assert_eq!(
606            pdf_dict.get("StrF"),
607            Some(&Object::Name("MyStringFilter".to_string()))
608        );
609    }
610
611    #[test]
612    fn test_multiple_crypt_filters() {
613        let mut enc_dict = EncryptionDictionary::rc4_128bit(
614            vec![0u8; 32],
615            vec![1u8; 32],
616            Permissions::new(),
617            None,
618        );
619
620        let filter1 = CryptFilter::standard(CryptFilterMethod::V2);
621        let filter2 = CryptFilter {
622            name: "AESFilter".to_string(),
623            method: CryptFilterMethod::AESV2,
624            length: Some(16),
625        };
626
627        enc_dict.cf = Some(vec![filter1, filter2]);
628
629        let pdf_dict = enc_dict.to_dict();
630        if let Some(Object::Dictionary(cf_dict)) = pdf_dict.get("CF") {
631            assert!(cf_dict.get("StdCF").is_some());
632            assert!(cf_dict.get("AESFilter").is_some());
633        } else {
634            panic!("CF should be a dictionary");
635        }
636    }
637}