Skip to main content

oxidize_pdf/parser/
encryption_handler.rs

1//! PDF encryption detection and password handling
2//!
3//! This module provides functionality to detect encrypted PDFs and handle password-based
4//! decryption according to ISO 32000-1 Chapter 7.6.
5
6use super::objects::PdfDictionary;
7use super::{ParseError, ParseResult};
8use crate::encryption::{
9    EncryptionKey, Permissions, Rc4, Rc4Key, StandardSecurityHandler, UserPassword,
10};
11use crate::objects::ObjectId;
12
13/// Encryption information extracted from PDF trailer
14#[derive(Debug, Clone)]
15pub struct EncryptionInfo {
16    /// Filter name (should be "Standard")
17    pub filter: String,
18    /// V entry (algorithm version)
19    pub v: i32,
20    /// R entry (revision)
21    pub r: i32,
22    /// O entry (owner password hash)
23    pub o: Vec<u8>,
24    /// U entry (user password hash)
25    pub u: Vec<u8>,
26    /// P entry (permissions)
27    pub p: i32,
28    /// Length entry (key length in bits)
29    pub length: Option<i32>,
30}
31
32/// PDF Encryption Handler
33pub struct EncryptionHandler {
34    /// Encryption information from trailer
35    encryption_info: EncryptionInfo,
36    /// Standard security handler
37    security_handler: StandardSecurityHandler,
38    /// Current encryption key (if unlocked)
39    encryption_key: Option<EncryptionKey>,
40    /// File ID from trailer
41    file_id: Option<Vec<u8>>,
42}
43
44impl EncryptionHandler {
45    /// Create encryption handler from encryption dictionary
46    pub fn new(encrypt_dict: &PdfDictionary, file_id: Option<Vec<u8>>) -> ParseResult<Self> {
47        let encryption_info = Self::parse_encryption_dict(encrypt_dict)?;
48
49        // Create appropriate security handler based on revision
50        let security_handler = match encryption_info.r {
51            2 => StandardSecurityHandler::rc4_40bit(),
52            3 | 4 => StandardSecurityHandler::rc4_128bit(),
53            5 => StandardSecurityHandler::aes_256_r5(),
54            6 => StandardSecurityHandler::aes_256_r6(),
55            _ => {
56                return Err(ParseError::SyntaxError {
57                    position: 0,
58                    message: format!("Encryption revision {} not supported", encryption_info.r),
59                });
60            }
61        };
62
63        Ok(Self {
64            encryption_info,
65            security_handler,
66            encryption_key: None,
67            file_id,
68        })
69    }
70
71    /// Parse encryption dictionary from PDF trailer
72    fn parse_encryption_dict(dict: &PdfDictionary) -> ParseResult<EncryptionInfo> {
73        // Get Filter (required)
74        let filter = dict
75            .get("Filter")
76            .and_then(|obj| obj.as_name())
77            .map(|name| name.0.as_str())
78            .ok_or_else(|| ParseError::MissingKey("Filter".to_string()))?;
79
80        if filter != "Standard" {
81            return Err(ParseError::SyntaxError {
82                position: 0,
83                message: format!("Encryption filter '{filter}' not supported"),
84            });
85        }
86
87        // Get V (algorithm version)
88        let v = dict
89            .get("V")
90            .and_then(|obj| obj.as_integer())
91            .map(|i| i as i32)
92            .unwrap_or(0);
93
94        // Get R (revision)
95        let r = dict
96            .get("R")
97            .and_then(|obj| obj.as_integer())
98            .map(|i| i as i32)
99            .ok_or_else(|| ParseError::MissingKey("R".to_string()))?;
100
101        // Get O (owner password hash)
102        let o = dict
103            .get("O")
104            .and_then(|obj| obj.as_string())
105            .ok_or_else(|| ParseError::MissingKey("O".to_string()))?
106            .as_bytes()
107            .to_vec();
108
109        // Get U (user password hash)
110        let u = dict
111            .get("U")
112            .and_then(|obj| obj.as_string())
113            .ok_or_else(|| ParseError::MissingKey("U".to_string()))?
114            .as_bytes()
115            .to_vec();
116
117        // Get P (permissions)
118        let p = dict
119            .get("P")
120            .and_then(|obj| obj.as_integer())
121            .map(|i| i as i32)
122            .ok_or_else(|| ParseError::MissingKey("P".to_string()))?;
123
124        // Get Length (optional, defaults based on revision)
125        let length = dict
126            .get("Length")
127            .and_then(|obj| obj.as_integer())
128            .map(|i| i as i32);
129
130        Ok(EncryptionInfo {
131            filter: filter.to_string(),
132            v,
133            r,
134            o,
135            u,
136            p,
137            length,
138        })
139    }
140
141    /// Check if PDF is encrypted by looking for Encrypt entry in trailer
142    pub fn detect_encryption(trailer: &PdfDictionary) -> bool {
143        trailer.contains_key("Encrypt")
144    }
145
146    /// Try to unlock PDF with user password
147    pub fn unlock_with_user_password(&mut self, password: &str) -> ParseResult<bool> {
148        let user_password = UserPassword(password.to_string());
149
150        // Compute what the U entry should be for this password
151        let permissions = Permissions::from_bits(self.encryption_info.p as u32);
152
153        // Debug: show inputs
154        #[cfg(debug_assertions)]
155        {
156            eprintln!("[DEBUG unlock] password len: {}", password.len());
157            eprintln!(
158                "[DEBUG unlock] O[0..8]: {:02x?}",
159                &self.encryption_info.o[..8.min(self.encryption_info.o.len())]
160            );
161            eprintln!(
162                "[DEBUG unlock] U[0..8]: {:02x?}",
163                &self.encryption_info.u[..8.min(self.encryption_info.u.len())]
164            );
165            eprintln!(
166                "[DEBUG unlock] P: {} (0x{:08X})",
167                self.encryption_info.p, self.encryption_info.p as u32
168            );
169            eprintln!("[DEBUG unlock] R: {}", self.encryption_info.r);
170            if let Some(ref fid) = self.file_id {
171                eprintln!(
172                    "[DEBUG unlock] file_id len: {}, first bytes: {:02x?}",
173                    fid.len(),
174                    &fid[..8.min(fid.len())]
175                );
176            } else {
177                eprintln!("[DEBUG unlock] file_id: None");
178            }
179        }
180
181        let computed_u = self
182            .security_handler
183            .compute_user_hash(
184                &user_password,
185                &self.encryption_info.o,
186                permissions,
187                self.file_id.as_deref(),
188            )
189            .map_err(|e| ParseError::SyntaxError {
190                position: 0,
191                message: format!("Failed to compute user hash: {e}"),
192            })?;
193
194        // Compare with stored U entry (first 16 bytes for R3+)
195        let comparison_length = if self.encryption_info.r >= 3 { 16 } else { 32 };
196
197        #[cfg(debug_assertions)]
198        {
199            eprintln!("[DEBUG unlock] computed_u[0..8]: {:02x?}", &computed_u[..8]);
200            eprintln!(
201                "[DEBUG unlock] stored_u[0..8]: {:02x?}",
202                &self.encryption_info.u[..8.min(self.encryption_info.u.len())]
203            );
204            eprintln!("[DEBUG unlock] comparison_length: {}", comparison_length);
205        }
206
207        let matches =
208            computed_u[..comparison_length] == self.encryption_info.u[..comparison_length];
209
210        if matches {
211            // Compute and store encryption key
212            let key = self
213                .security_handler
214                .compute_encryption_key(
215                    &user_password,
216                    &self.encryption_info.o,
217                    permissions,
218                    self.file_id.as_deref(),
219                )
220                .map_err(|e| ParseError::SyntaxError {
221                    position: 0,
222                    message: format!("Failed to compute encryption key: {e}"),
223                })?;
224            self.encryption_key = Some(key);
225        }
226
227        Ok(matches)
228    }
229
230    /// Try to unlock PDF with owner password
231    ///
232    /// Owner password authentication works by:
233    /// 1. Deriving an RC4 key from the owner password
234    /// 2. Decrypting the O entry to recover the user password
235    /// 3. Using the recovered user password to compute the encryption key
236    pub fn unlock_with_owner_password(&mut self, password: &str) -> ParseResult<bool> {
237        // Standard 32-byte padding from PDF spec
238        const PADDING: [u8; 32] = [
239            0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA,
240            0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE,
241            0x64, 0x53, 0x69, 0x7A,
242        ];
243
244        // Step 1: Pad owner password to 32 bytes
245        let mut padded = [0u8; 32];
246        let password_bytes = password.as_bytes();
247        let len = password_bytes.len().min(32);
248        padded[..len].copy_from_slice(&password_bytes[..len]);
249        if len < 32 {
250            padded[len..].copy_from_slice(&PADDING[..32 - len]);
251        }
252
253        // Step 2: MD5 hash the padded password
254        let mut hash = md5::compute(&padded).to_vec();
255
256        // Step 3: For R3+, do 50 additional MD5 iterations
257        let key_length = self.security_handler.key_length;
258        if self.encryption_info.r >= 3 {
259            for _ in 0..50 {
260                hash = md5::compute(&hash).to_vec();
261            }
262        }
263
264        // Step 4: Decrypt O entry to get user password
265        let mut decrypted = self.encryption_info.o[..32].to_vec();
266
267        if self.encryption_info.r >= 3 {
268            // For R3+, decrypt with keys XOR'd with 19, 18, ..., 0
269            for i in (0..20).rev() {
270                let mut key_bytes = hash[..key_length].to_vec();
271                for byte in &mut key_bytes {
272                    *byte ^= i as u8;
273                }
274                let rc4_key = Rc4Key::from_slice(&key_bytes);
275                let mut cipher = Rc4::new(&rc4_key);
276                decrypted = cipher.process(&decrypted);
277            }
278        } else {
279            // For R2, single RC4 decryption
280            let rc4_key = Rc4Key::from_slice(&hash[..key_length]);
281            let mut cipher = Rc4::new(&rc4_key);
282            decrypted = cipher.process(&decrypted);
283        }
284
285        // Step 5: The decrypted data is the padded user password
286        // Find where padding starts to extract the actual password
287        let user_pwd_end = decrypted
288            .iter()
289            .position(|&b| {
290                // Check if this byte is the start of the padding sequence
291                b == PADDING[0] && decrypted.len() > 1
292            })
293            .unwrap_or(32);
294
295        let user_password = String::from_utf8_lossy(&decrypted[..user_pwd_end]).to_string();
296
297        #[cfg(debug_assertions)]
298        {
299            eprintln!(
300                "[DEBUG owner unlock] derived user password: {:?}",
301                &user_password
302            );
303            eprintln!(
304                "[DEBUG owner unlock] decrypted bytes: {:02x?}",
305                &decrypted[..8]
306            );
307        }
308
309        // Step 6: Try to unlock with the derived user password
310        if self.unlock_with_user_password(&user_password)? {
311            return Ok(true);
312        }
313
314        // If the derived password didn't work, try with the full padded password
315        // (some PDFs may use the raw padded form)
316        let full_user_password = String::from_utf8_lossy(&decrypted).to_string();
317        self.unlock_with_user_password(&full_user_password)
318    }
319
320    /// Try to unlock with empty password (common case)
321    pub fn try_empty_password(&mut self) -> ParseResult<bool> {
322        self.unlock_with_user_password("")
323    }
324
325    /// Check if the PDF is currently unlocked
326    pub fn is_unlocked(&self) -> bool {
327        self.encryption_key.is_some()
328    }
329
330    /// Get the current encryption key (if unlocked)
331    pub fn encryption_key(&self) -> Option<&EncryptionKey> {
332        self.encryption_key.as_ref()
333    }
334
335    /// Decrypt a string object
336    pub fn decrypt_string(&self, data: &[u8], obj_id: &ObjectId) -> ParseResult<Vec<u8>> {
337        match &self.encryption_key {
338            Some(key) => Ok(self.security_handler.decrypt_string(data, key, obj_id)),
339            None => Err(ParseError::EncryptionNotSupported),
340        }
341    }
342
343    /// Decrypt a stream object
344    pub fn decrypt_stream(&self, data: &[u8], obj_id: &ObjectId) -> ParseResult<Vec<u8>> {
345        match &self.encryption_key {
346            Some(key) => Ok(self.security_handler.decrypt_stream(data, key, obj_id)),
347            None => Err(ParseError::EncryptionNotSupported),
348        }
349    }
350
351    /// Get encryption algorithm information
352    pub fn algorithm_info(&self) -> String {
353        match (
354            self.encryption_info.r,
355            self.encryption_info.length.unwrap_or(40),
356        ) {
357            (2, _) => "RC4 40-bit".to_string(),
358            (3, len) => format!("RC4 {len}-bit"),
359            (4, len) => format!("RC4 {len}-bit with metadata control"),
360            (5, _) => "AES-256 (Revision 5)".to_string(),
361            (6, _) => "AES-256 (Revision 6, Unicode passwords)".to_string(),
362            (r, len) => format!("Unknown revision {r} with {len}-bit key"),
363        }
364    }
365
366    /// Get permissions information
367    pub fn permissions(&self) -> Permissions {
368        Permissions::from_bits(self.encryption_info.p as u32)
369    }
370
371    /// Check if file ID is available
372    pub fn has_file_id(&self) -> bool {
373        self.file_id.is_some()
374    }
375
376    /// Get the encryption revision
377    pub fn revision(&self) -> i32 {
378        self.encryption_info.r
379    }
380
381    /// Check if strings should be encrypted
382    pub fn encrypt_strings(&self) -> bool {
383        // For standard security handler, strings are always encrypted
384        true
385    }
386
387    /// Check if streams should be encrypted  
388    pub fn encrypt_streams(&self) -> bool {
389        // For standard security handler, streams are always encrypted
390        true
391    }
392
393    /// Check if metadata should be encrypted (R4 only)
394    pub fn encrypt_metadata(&self) -> bool {
395        // For R4, this could be controlled by EncryptMetadata entry
396        // For now, assume metadata is encrypted
397        true
398    }
399}
400
401/// Password prompt result
402#[derive(Debug)]
403pub enum PasswordResult {
404    /// Password was accepted
405    Success,
406    /// Password was rejected
407    Rejected,
408    /// User cancelled password entry
409    Cancelled,
410}
411
412/// Trait for password prompting
413pub trait PasswordProvider {
414    /// Prompt for user password
415    fn prompt_user_password(&self) -> ParseResult<Option<String>>;
416
417    /// Prompt for owner password
418    fn prompt_owner_password(&self) -> ParseResult<Option<String>>;
419}
420
421/// Console-based password provider
422pub struct ConsolePasswordProvider;
423
424impl PasswordProvider for ConsolePasswordProvider {
425    fn prompt_user_password(&self) -> ParseResult<Option<String>> {
426        tracing::debug!(
427            "PDF is encrypted. Enter user password (or press Enter for empty password):"
428        );
429
430        let mut input = String::new();
431        std::io::stdin()
432            .read_line(&mut input)
433            .map_err(|e| ParseError::SyntaxError {
434                position: 0,
435                message: format!("Failed to read password: {e}"),
436            })?;
437
438        // Remove trailing newline
439        input.truncate(input.trim_end().len());
440        Ok(Some(input))
441    }
442
443    fn prompt_owner_password(&self) -> ParseResult<Option<String>> {
444        tracing::debug!("User password failed. Enter owner password:");
445
446        let mut input = String::new();
447        std::io::stdin()
448            .read_line(&mut input)
449            .map_err(|e| ParseError::SyntaxError {
450                position: 0,
451                message: format!("Failed to read password: {e}"),
452            })?;
453
454        // Remove trailing newline
455        input.truncate(input.trim_end().len());
456        Ok(Some(input))
457    }
458}
459
460/// Interactive decryption helper
461pub struct InteractiveDecryption<P: PasswordProvider> {
462    password_provider: P,
463}
464
465impl<P: PasswordProvider> InteractiveDecryption<P> {
466    /// Create new interactive decryption helper
467    pub fn new(password_provider: P) -> Self {
468        Self { password_provider }
469    }
470
471    /// Attempt to unlock PDF interactively
472    pub fn unlock_pdf(&self, handler: &mut EncryptionHandler) -> ParseResult<PasswordResult> {
473        // First try empty password
474        if handler.try_empty_password()? {
475            return Ok(PasswordResult::Success);
476        }
477
478        // Try user password
479        if let Some(password) = self.password_provider.prompt_user_password()? {
480            if handler.unlock_with_user_password(&password)? {
481                return Ok(PasswordResult::Success);
482            }
483        } else {
484            return Ok(PasswordResult::Cancelled);
485        }
486
487        // Try owner password
488        if let Some(password) = self.password_provider.prompt_owner_password()? {
489            if handler.unlock_with_owner_password(&password)? {
490                return Ok(PasswordResult::Success);
491            }
492        } else {
493            return Ok(PasswordResult::Cancelled);
494        }
495
496        Ok(PasswordResult::Rejected)
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503    use crate::parser::objects::{PdfDictionary, PdfName, PdfObject, PdfString};
504
505    fn create_test_encryption_dict() -> PdfDictionary {
506        let mut dict = PdfDictionary::new();
507        dict.insert(
508            "Filter".to_string(),
509            PdfObject::Name(PdfName("Standard".to_string())),
510        );
511        dict.insert("V".to_string(), PdfObject::Integer(1));
512        dict.insert("R".to_string(), PdfObject::Integer(2));
513        dict.insert(
514            "O".to_string(),
515            PdfObject::String(PdfString::new(vec![0u8; 32])),
516        );
517        dict.insert(
518            "U".to_string(),
519            PdfObject::String(PdfString::new(vec![0u8; 32])),
520        );
521        dict.insert("P".to_string(), PdfObject::Integer(-4));
522        dict
523    }
524
525    #[test]
526    fn test_encryption_detection() {
527        let mut trailer = PdfDictionary::new();
528        assert!(!EncryptionHandler::detect_encryption(&trailer));
529
530        trailer.insert("Encrypt".to_string(), PdfObject::Reference(1, 0));
531        assert!(EncryptionHandler::detect_encryption(&trailer));
532    }
533
534    #[test]
535    fn test_encryption_info_parsing() {
536        let dict = create_test_encryption_dict();
537        let info = EncryptionHandler::parse_encryption_dict(&dict).unwrap();
538
539        assert_eq!(info.filter, "Standard");
540        assert_eq!(info.v, 1);
541        assert_eq!(info.r, 2);
542        assert_eq!(info.o.len(), 32);
543        assert_eq!(info.u.len(), 32);
544        assert_eq!(info.p, -4);
545    }
546
547    #[test]
548    fn test_encryption_handler_creation() {
549        let dict = create_test_encryption_dict();
550        let handler = EncryptionHandler::new(&dict, None).unwrap();
551
552        assert_eq!(handler.encryption_info.r, 2);
553        assert!(!handler.is_unlocked());
554        assert_eq!(handler.algorithm_info(), "RC4 40-bit");
555    }
556
557    #[test]
558    fn test_empty_password_attempt() {
559        let dict = create_test_encryption_dict();
560        let mut handler = EncryptionHandler::new(&dict, None).unwrap();
561
562        // Empty password should not work with test data
563        let result = handler.try_empty_password().unwrap();
564        assert!(!result);
565        assert!(!handler.is_unlocked());
566    }
567
568    #[test]
569    fn test_permissions() {
570        let dict = create_test_encryption_dict();
571        let handler = EncryptionHandler::new(&dict, None).unwrap();
572
573        let permissions = handler.permissions();
574        // P value of -4 should result in specific permissions
575        assert!(permissions.bits() != 0);
576    }
577
578    #[test]
579    fn test_encryption_flags() {
580        let dict = create_test_encryption_dict();
581        let handler = EncryptionHandler::new(&dict, None).unwrap();
582
583        assert!(handler.encrypt_strings());
584        assert!(handler.encrypt_streams());
585        assert!(handler.encrypt_metadata());
586    }
587
588    #[test]
589    fn test_decrypt_without_key() {
590        let dict = create_test_encryption_dict();
591        let handler = EncryptionHandler::new(&dict, None).unwrap();
592
593        let obj_id = ObjectId::new(1, 0);
594        let result = handler.decrypt_string(b"test", &obj_id);
595        assert!(result.is_err());
596    }
597
598    #[test]
599    fn test_unsupported_filter() {
600        let mut dict = PdfDictionary::new();
601        dict.insert(
602            "Filter".to_string(),
603            PdfObject::Name(PdfName("UnsupportedFilter".to_string())),
604        );
605        dict.insert("R".to_string(), PdfObject::Integer(2));
606        dict.insert(
607            "O".to_string(),
608            PdfObject::String(PdfString::new(vec![0u8; 32])),
609        );
610        dict.insert(
611            "U".to_string(),
612            PdfObject::String(PdfString::new(vec![0u8; 32])),
613        );
614        dict.insert("P".to_string(), PdfObject::Integer(-4));
615
616        let result = EncryptionHandler::new(&dict, None);
617        assert!(result.is_err());
618    }
619
620    #[test]
621    fn test_unsupported_revision() {
622        let mut dict = create_test_encryption_dict();
623        dict.insert("R".to_string(), PdfObject::Integer(99)); // Unsupported revision
624
625        let result = EncryptionHandler::new(&dict, None);
626        assert!(result.is_err());
627    }
628
629    #[test]
630    fn test_missing_required_keys() {
631        let test_cases = vec![
632            ("Filter", PdfObject::Name(PdfName("Standard".to_string()))),
633            ("R", PdfObject::Integer(2)),
634            ("O", PdfObject::String(PdfString::new(vec![0u8; 32]))),
635            ("U", PdfObject::String(PdfString::new(vec![0u8; 32]))),
636            ("P", PdfObject::Integer(-4)),
637        ];
638
639        for (skip_key, _) in test_cases {
640            let mut dict = create_test_encryption_dict();
641            dict.0.remove(&PdfName(skip_key.to_string()));
642
643            let result = EncryptionHandler::parse_encryption_dict(&dict);
644            assert!(result.is_err(), "Should fail when {skip_key} is missing");
645        }
646    }
647
648    /// Mock password provider for testing
649    struct MockPasswordProvider {
650        user_password: Option<String>,
651        owner_password: Option<String>,
652    }
653
654    impl PasswordProvider for MockPasswordProvider {
655        fn prompt_user_password(&self) -> ParseResult<Option<String>> {
656            Ok(self.user_password.clone())
657        }
658
659        fn prompt_owner_password(&self) -> ParseResult<Option<String>> {
660            Ok(self.owner_password.clone())
661        }
662    }
663
664    #[test]
665    fn test_interactive_decryption_cancelled() {
666        let provider = MockPasswordProvider {
667            user_password: None,
668            owner_password: None,
669        };
670
671        let decryption = InteractiveDecryption::new(provider);
672        let dict = create_test_encryption_dict();
673        let mut handler = EncryptionHandler::new(&dict, None).unwrap();
674
675        let result = decryption.unlock_pdf(&mut handler).unwrap();
676        matches!(result, PasswordResult::Cancelled);
677    }
678
679    #[test]
680    fn test_interactive_decryption_rejected() {
681        let provider = MockPasswordProvider {
682            user_password: Some("wrong_password".to_string()),
683            owner_password: Some("also_wrong".to_string()),
684        };
685
686        let decryption = InteractiveDecryption::new(provider);
687        let dict = create_test_encryption_dict();
688        let mut handler = EncryptionHandler::new(&dict, None).unwrap();
689
690        let result = decryption.unlock_pdf(&mut handler).unwrap();
691        matches!(result, PasswordResult::Rejected);
692    }
693
694    // ===== ADVANCED EDGE CASE TESTS =====
695
696    #[test]
697    fn test_malformed_encryption_dictionary_invalid_types() {
698        // Test with wrong type for Filter
699        let mut dict = PdfDictionary::new();
700        dict.insert("Filter".to_string(), PdfObject::Integer(123)); // Should be Name
701        dict.insert("R".to_string(), PdfObject::Integer(2));
702        dict.insert(
703            "O".to_string(),
704            PdfObject::String(PdfString::new(vec![0u8; 32])),
705        );
706        dict.insert(
707            "U".to_string(),
708            PdfObject::String(PdfString::new(vec![0u8; 32])),
709        );
710        dict.insert("P".to_string(), PdfObject::Integer(-4));
711
712        let result = EncryptionHandler::parse_encryption_dict(&dict);
713        assert!(result.is_err());
714
715        // Test with wrong type for R
716        let mut dict = create_test_encryption_dict();
717        dict.insert(
718            "R".to_string(),
719            PdfObject::Name(PdfName("not_a_number".to_string())),
720        );
721        let result = EncryptionHandler::parse_encryption_dict(&dict);
722        assert!(result.is_err());
723    }
724
725    #[test]
726    fn test_encryption_dictionary_edge_values() {
727        // Test with extreme revision values
728        let mut dict = create_test_encryption_dict();
729        dict.insert("R".to_string(), PdfObject::Integer(0)); // Very low revision
730        let result = EncryptionHandler::new(&dict, None);
731        assert!(result.is_err());
732
733        // Test with negative revision
734        let mut dict = create_test_encryption_dict();
735        dict.insert("R".to_string(), PdfObject::Integer(-1));
736        let result = EncryptionHandler::new(&dict, None);
737        assert!(result.is_err());
738
739        // Test with very high revision
740        let mut dict = create_test_encryption_dict();
741        dict.insert("R".to_string(), PdfObject::Integer(1000));
742        let result = EncryptionHandler::new(&dict, None);
743        assert!(result.is_err());
744    }
745
746    #[test]
747    fn test_encryption_dictionary_invalid_hash_lengths() {
748        // Test with O hash too short
749        let mut dict = create_test_encryption_dict();
750        dict.insert(
751            "O".to_string(),
752            PdfObject::String(PdfString::new(vec![0u8; 16])),
753        ); // Should be 32
754        let result = EncryptionHandler::parse_encryption_dict(&dict);
755        // Should still work but be invalid data
756        assert!(result.is_ok());
757
758        // Test with U hash too long
759        let mut dict = create_test_encryption_dict();
760        dict.insert(
761            "U".to_string(),
762            PdfObject::String(PdfString::new(vec![0u8; 64])),
763        ); // Should be 32
764        let result = EncryptionHandler::parse_encryption_dict(&dict);
765        assert!(result.is_ok());
766
767        // Test with empty hashes
768        let mut dict = create_test_encryption_dict();
769        dict.insert("O".to_string(), PdfObject::String(PdfString::new(vec![])));
770        dict.insert("U".to_string(), PdfObject::String(PdfString::new(vec![])));
771        let result = EncryptionHandler::parse_encryption_dict(&dict);
772        assert!(result.is_ok());
773    }
774
775    #[test]
776    fn test_encryption_with_different_key_lengths() {
777        // Test Rev 2 (40-bit)
778        let mut dict = create_test_encryption_dict();
779        dict.insert("R".to_string(), PdfObject::Integer(2));
780        let handler = EncryptionHandler::new(&dict, None).unwrap();
781        assert_eq!(handler.algorithm_info(), "RC4 40-bit");
782
783        // Test Rev 3 (128-bit)
784        let mut dict = create_test_encryption_dict();
785        dict.insert("R".to_string(), PdfObject::Integer(3));
786        dict.insert("Length".to_string(), PdfObject::Integer(128));
787        let handler = EncryptionHandler::new(&dict, None).unwrap();
788        assert_eq!(handler.algorithm_info(), "RC4 128-bit");
789
790        // Test Rev 4 (128-bit with metadata control)
791        let mut dict = create_test_encryption_dict();
792        dict.insert("R".to_string(), PdfObject::Integer(4));
793        dict.insert("Length".to_string(), PdfObject::Integer(128));
794        let handler = EncryptionHandler::new(&dict, None).unwrap();
795        assert_eq!(
796            handler.algorithm_info(),
797            "RC4 128-bit with metadata control"
798        );
799
800        // Test Rev 5 (AES-256)
801        let mut dict = create_test_encryption_dict();
802        dict.insert("R".to_string(), PdfObject::Integer(5));
803        dict.insert("V".to_string(), PdfObject::Integer(5));
804        let handler = EncryptionHandler::new(&dict, None).unwrap();
805        assert_eq!(handler.algorithm_info(), "AES-256 (Revision 5)");
806
807        // Test Rev 6 (AES-256 with Unicode)
808        let mut dict = create_test_encryption_dict();
809        dict.insert("R".to_string(), PdfObject::Integer(6));
810        dict.insert("V".to_string(), PdfObject::Integer(5));
811        let handler = EncryptionHandler::new(&dict, None).unwrap();
812        assert_eq!(
813            handler.algorithm_info(),
814            "AES-256 (Revision 6, Unicode passwords)"
815        );
816    }
817
818    #[test]
819    fn test_file_id_handling() {
820        let dict = create_test_encryption_dict();
821
822        // Test with file ID
823        let file_id = Some(b"test_file_id_12345678".to_vec());
824        let handler = EncryptionHandler::new(&dict, file_id.clone()).unwrap();
825        // File ID should be stored
826        assert_eq!(handler.file_id, file_id);
827
828        // Test without file ID
829        let handler = EncryptionHandler::new(&dict, None).unwrap();
830        assert_eq!(handler.file_id, None);
831
832        // Test with empty file ID
833        let empty_file_id = Some(vec![]);
834        let handler = EncryptionHandler::new(&dict, empty_file_id.clone()).unwrap();
835        assert_eq!(handler.file_id, empty_file_id);
836    }
837
838    #[test]
839    fn test_permissions_edge_cases() {
840        // Test with different permission values
841        let permission_values = vec![0, -1, -4, -44, -100, i32::MAX, i32::MIN];
842
843        for p_value in permission_values {
844            let mut dict = create_test_encryption_dict();
845            dict.insert("P".to_string(), PdfObject::Integer(p_value as i64));
846            let handler = EncryptionHandler::new(&dict, None).unwrap();
847
848            let permissions = handler.permissions();
849            assert_eq!(permissions.bits(), p_value as u32);
850        }
851    }
852
853    #[test]
854    fn test_decrypt_with_different_object_ids() {
855        let dict = create_test_encryption_dict();
856        let handler = EncryptionHandler::new(&dict, None).unwrap();
857        let test_data = b"test data";
858
859        // Test with different object IDs (should all fail since not unlocked)
860        let object_ids = vec![
861            ObjectId::new(1, 0),
862            ObjectId::new(999, 0),
863            ObjectId::new(1, 999),
864            ObjectId::new(u32::MAX, u16::MAX),
865        ];
866
867        for obj_id in object_ids {
868            let result = handler.decrypt_string(test_data, &obj_id);
869            assert!(result.is_err());
870
871            let result = handler.decrypt_stream(test_data, &obj_id);
872            assert!(result.is_err());
873        }
874    }
875
876    #[test]
877    fn test_password_scenarios_comprehensive() {
878        let dict = create_test_encryption_dict();
879        let mut handler = EncryptionHandler::new(&dict, None).unwrap();
880
881        // Test various password scenarios (using String for uniformity)
882        let test_passwords = vec![
883            "".to_string(),                     // Empty
884            " ".to_string(),                    // Single space
885            "   ".to_string(),                  // Multiple spaces
886            "password".to_string(),             // Simple
887            "Password123!@#".to_string(),       // Complex
888            "a".repeat(32),                     // Exactly 32 chars
889            "a".repeat(50),                     // Over 32 chars
890            "unicode_ñáéíóú".to_string(),       // Unicode
891            "pass\nwith\nnewlines".to_string(), // Newlines
892            "pass\twith\ttabs".to_string(),     // Tabs
893            "pass with spaces".to_string(),     // Spaces
894            "🔐🗝️📄".to_string(),               // Emojis
895        ];
896
897        for password in test_passwords {
898            // All should fail with test data but not crash
899            let result = handler.unlock_with_user_password(&password);
900            assert!(result.is_ok());
901            assert!(!result.unwrap());
902
903            let result = handler.unlock_with_owner_password(&password);
904            assert!(result.is_ok());
905            assert!(!result.unwrap());
906        }
907    }
908
909    #[test]
910    fn test_encryption_handler_thread_safety_simulation() {
911        // Simulate what would happen in multi-threaded access
912        let dict = create_test_encryption_dict();
913        let handler = EncryptionHandler::new(&dict, None).unwrap();
914
915        // Test multiple read operations (safe)
916        for _ in 0..100 {
917            assert!(!handler.is_unlocked());
918            assert_eq!(handler.algorithm_info(), "RC4 40-bit");
919            assert!(handler.encrypt_strings());
920            assert!(handler.encrypt_streams());
921            assert!(handler.encrypt_metadata());
922        }
923    }
924
925    #[test]
926    fn test_encryption_state_transitions() {
927        let dict = create_test_encryption_dict();
928        let mut handler = EncryptionHandler::new(&dict, None).unwrap();
929
930        // Initial state
931        assert!(!handler.is_unlocked());
932
933        // Try unlock (should fail with test data)
934        let result = handler.try_empty_password().unwrap();
935        assert!(!result);
936        assert!(!handler.is_unlocked());
937
938        // Try user password (should fail with test data)
939        let result = handler.unlock_with_user_password("test").unwrap();
940        assert!(!result);
941        assert!(!handler.is_unlocked());
942
943        // Try owner password (should fail with test data)
944        let result = handler.unlock_with_owner_password("test").unwrap();
945        assert!(!result);
946        assert!(!handler.is_unlocked());
947
948        // State should remain consistent
949        assert!(!handler.is_unlocked());
950    }
951
952    #[test]
953    fn test_interactive_decryption_edge_cases() {
954        // Test provider that returns None for both passwords
955        let provider = MockPasswordProvider {
956            user_password: None,
957            owner_password: None,
958        };
959
960        let decryption = InteractiveDecryption::new(provider);
961        let dict = create_test_encryption_dict();
962        let mut handler = EncryptionHandler::new(&dict, None).unwrap();
963
964        let result = decryption.unlock_pdf(&mut handler).unwrap();
965        matches!(result, PasswordResult::Cancelled);
966
967        // Test provider that returns empty strings
968        let provider = MockPasswordProvider {
969            user_password: Some("".to_string()),
970            owner_password: Some("".to_string()),
971        };
972
973        let decryption = InteractiveDecryption::new(provider);
974        let mut handler = EncryptionHandler::new(&dict, None).unwrap();
975
976        let result = decryption.unlock_pdf(&mut handler).unwrap();
977        matches!(result, PasswordResult::Rejected);
978    }
979
980    /// Test custom MockPasswordProvider for edge cases
981    struct EdgeCasePasswordProvider {
982        call_count: std::cell::RefCell<usize>,
983        passwords: Vec<Option<String>>,
984    }
985
986    impl EdgeCasePasswordProvider {
987        fn new(passwords: Vec<Option<String>>) -> Self {
988            Self {
989                call_count: std::cell::RefCell::new(0),
990                passwords,
991            }
992        }
993    }
994
995    impl PasswordProvider for EdgeCasePasswordProvider {
996        fn prompt_user_password(&self) -> ParseResult<Option<String>> {
997            let mut count = self.call_count.borrow_mut();
998            if *count < self.passwords.len() {
999                let result = self.passwords[*count].clone();
1000                *count += 1;
1001                Ok(result)
1002            } else {
1003                Ok(None)
1004            }
1005        }
1006
1007        fn prompt_owner_password(&self) -> ParseResult<Option<String>> {
1008            self.prompt_user_password()
1009        }
1010    }
1011
1012    #[test]
1013    fn test_interactive_decryption_with_sequence() {
1014        let passwords = vec![
1015            Some("first_attempt".to_string()),
1016            Some("second_attempt".to_string()),
1017            None, // Cancelled
1018        ];
1019
1020        let provider = EdgeCasePasswordProvider::new(passwords);
1021        let decryption = InteractiveDecryption::new(provider);
1022        let dict = create_test_encryption_dict();
1023        let mut handler = EncryptionHandler::new(&dict, None).unwrap();
1024
1025        let result = decryption.unlock_pdf(&mut handler).unwrap();
1026        matches!(result, PasswordResult::Cancelled);
1027    }
1028}