Skip to main content

oxidize_pdf/encryption/
encryption_dict.rs

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