Skip to main content

fop_render/pdf/
security.rs

1//! PDF security and encryption support
2//!
3//! Implements PDF encryption per PDF specification:
4//!   - RC4-128  (PDF 1.4, /V 2 /R 3)
5//!   - AES-256  (PDF 2.0, /V 5 /R 6)
6//!
7//! Supports owner/user passwords and permission flags (PDF spec Table 3.20).
8//!
9//! Pure Rust implementation using `aes`, `cbc`, `sha2`, and `md5` crates — no C FFI.
10
11use aes::cipher::{BlockEncrypt, KeyInit};
12use aes::{Aes128, Aes256};
13use cbc::cipher::{BlockEncryptMut, KeyIvInit};
14use md5::Md5;
15use sha2::Digest as _;
16use sha2::Sha256;
17
18// ---------------------------------------------------------------------------
19// Encryption algorithm selector
20// ---------------------------------------------------------------------------
21
22/// PDF encryption algorithm
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum EncryptionAlgorithm {
25    /// RC4-128 (PDF 1.4, /V 2 /R 3) — legacy, default for backwards compatibility
26    #[default]
27    Rc4128,
28    /// AES-256 (PDF 2.0, /V 5 /R 6) — modern, recommended
29    Aes256,
30}
31
32// ---------------------------------------------------------------------------
33// PDF permission flags
34// ---------------------------------------------------------------------------
35
36/// PDF permission flags per PDF spec Table 3.20
37///
38/// These flags control what operations are allowed when the document
39/// is opened with the user password.
40#[derive(Debug, Clone, Copy)]
41pub struct PdfPermissions {
42    /// Allow printing (bit 3)
43    pub allow_print: bool,
44    /// Allow modifying content (bit 4)
45    pub allow_modify: bool,
46    /// Allow copying/extracting text (bit 5)
47    pub allow_copy: bool,
48    /// Allow adding/modifying annotations (bit 6)
49    pub allow_annotations: bool,
50    /// Allow filling form fields (bit 9) — always true for rev 3+
51    pub allow_fill_forms: bool,
52    /// Allow extracting for accessibility (bit 10)
53    pub allow_accessibility: bool,
54    /// Allow assembling document (bit 11)
55    pub allow_assemble: bool,
56    /// Allow high-quality printing (bit 12)
57    pub allow_print_high_quality: bool,
58}
59
60impl Default for PdfPermissions {
61    fn default() -> Self {
62        Self {
63            allow_print: true,
64            allow_modify: true,
65            allow_copy: true,
66            allow_annotations: true,
67            allow_fill_forms: true,
68            allow_accessibility: true,
69            allow_assemble: true,
70            allow_print_high_quality: true,
71        }
72    }
73}
74
75impl PdfPermissions {
76    /// Convert permissions to the 32-bit integer used in the /P entry
77    ///
78    /// Per PDF spec, bits 1-2 must be 0, bits 7-8 are reserved (1),
79    /// bits 13-32 are reserved (1 for rev 3+).
80    pub fn to_p_value(&self) -> i32 {
81        let mut p: u32 = 0xFFFFF000; // Bits 13-32 all set
82
83        // Bits 7-8 reserved, set to 1
84        p |= 0b1100_0000;
85
86        if self.allow_print {
87            p |= 1 << 2; // bit 3
88        }
89        if self.allow_modify {
90            p |= 1 << 3; // bit 4
91        }
92        if self.allow_copy {
93            p |= 1 << 4; // bit 5
94        }
95        if self.allow_annotations {
96            p |= 1 << 5; // bit 6
97        }
98        if self.allow_fill_forms {
99            p |= 1 << 8; // bit 9
100        }
101        if self.allow_accessibility {
102            p |= 1 << 9; // bit 10
103        }
104        if self.allow_assemble {
105            p |= 1 << 10; // bit 11
106        }
107        if self.allow_print_high_quality {
108            p |= 1 << 11; // bit 12
109        }
110
111        p as i32
112    }
113}
114
115// ---------------------------------------------------------------------------
116// PdfSecurity
117// ---------------------------------------------------------------------------
118
119/// PDF encryption settings
120#[derive(Debug, Clone)]
121pub struct PdfSecurity {
122    /// Owner password (controls permissions)
123    pub owner_password: String,
124    /// User password (required to open document)
125    pub user_password: String,
126    /// Permission flags
127    pub permissions: PdfPermissions,
128    /// Encryption key length in bits (40 or 128 for RC4; 256 for AES)
129    pub key_length: u32,
130    /// Encryption algorithm
131    pub algorithm: EncryptionAlgorithm,
132}
133
134impl PdfSecurity {
135    /// Create new security settings with RC4-128 encryption (legacy default)
136    pub fn new(owner_password: &str, user_password: &str, permissions: PdfPermissions) -> Self {
137        Self {
138            owner_password: owner_password.to_string(),
139            user_password: user_password.to_string(),
140            permissions,
141            key_length: 128,
142            algorithm: EncryptionAlgorithm::Rc4128,
143        }
144    }
145
146    /// Create new security settings with AES-256 encryption (PDF 2.0)
147    pub fn new_aes256(
148        owner_password: &str,
149        user_password: &str,
150        permissions: PdfPermissions,
151    ) -> Self {
152        Self {
153            owner_password: owner_password.to_string(),
154            user_password: user_password.to_string(),
155            permissions,
156            key_length: 256,
157            algorithm: EncryptionAlgorithm::Aes256,
158        }
159    }
160
161    /// Compute the encryption dictionary entries
162    ///
163    /// Dispatches to the appropriate algorithm implementation.
164    pub fn compute_encryption_dict(&self, file_id: &[u8]) -> EncryptionDict {
165        match self.algorithm {
166            EncryptionAlgorithm::Rc4128 => self.compute_rc4_encryption_dict(file_id),
167            EncryptionAlgorithm::Aes256 => self.compute_aes256_encryption_dict(),
168        }
169    }
170
171    // ------------------------------------------------------------------
172    // RC4-128 implementation (PDF 1.4, /V 2 /R 3)
173    // ------------------------------------------------------------------
174
175    /// Compute RC4-128 encryption dictionary (Algorithms 3.2–3.5)
176    fn compute_rc4_encryption_dict(&self, file_id: &[u8]) -> EncryptionDict {
177        let p_value = self.permissions.to_p_value();
178        let revision = if self.key_length > 40 { 3 } else { 2 };
179        let version = if self.key_length > 40 { 2 } else { 1 };
180        let key_len_bytes = (self.key_length / 8) as usize;
181
182        // Step 1: Compute O value (Algorithm 3.3)
183        let o_value = self.compute_o_value(revision, key_len_bytes);
184
185        // Step 2: Compute encryption key (Algorithm 3.2)
186        let encryption_key =
187            self.compute_encryption_key(&o_value, p_value, file_id, revision, key_len_bytes);
188
189        // Step 3: Compute U value (Algorithm 3.4/3.5)
190        let u_value = self.compute_u_value(&encryption_key, file_id, revision);
191
192        EncryptionDict {
193            o_value,
194            u_value,
195            oe_value: None,
196            ue_value: None,
197            perms_value: None,
198            p_value,
199            encryption_key,
200            key_length: self.key_length,
201            revision: revision as u32,
202            version: version as u32,
203            algorithm: EncryptionAlgorithm::Rc4128,
204        }
205    }
206
207    /// Compute O value (owner password hash) per Algorithm 3.3
208    fn compute_o_value(&self, revision: usize, key_len_bytes: usize) -> Vec<u8> {
209        // Step a: Pad or truncate owner password
210        let owner_padded = pad_password(&self.owner_password);
211
212        // Step b: MD5 hash of padded password
213        let mut hash = md5_hash(&owner_padded);
214
215        // Step c: For revision 3, rehash 50 times
216        if revision >= 3 {
217            for _ in 0..50 {
218                hash = md5_hash(&hash[..key_len_bytes]);
219            }
220        }
221
222        // Step d: Use first key_len_bytes as RC4 key
223        let key = &hash[..key_len_bytes];
224
225        // Step e: Pad or truncate user password
226        let user_padded = pad_password(&self.user_password);
227
228        // Step f: RC4-encrypt the padded user password
229        let mut result = rc4_encrypt(key, &user_padded);
230
231        // Step g: For revision 3, iterate RC4 with XOR'd keys
232        if revision >= 3 {
233            for i in 1..=19 {
234                let derived_key: Vec<u8> = key.iter().map(|&b| b ^ (i as u8)).collect();
235                result = rc4_encrypt(&derived_key, &result);
236            }
237        }
238
239        result
240    }
241
242    /// Compute encryption key per Algorithm 3.2
243    fn compute_encryption_key(
244        &self,
245        o_value: &[u8],
246        p_value: i32,
247        file_id: &[u8],
248        revision: usize,
249        key_len_bytes: usize,
250    ) -> Vec<u8> {
251        let mut hasher = Md5::new();
252
253        // Step a: Pad the user password
254        let user_padded = pad_password(&self.user_password);
255        hasher.update(&user_padded);
256
257        // Step b: O value
258        hasher.update(o_value);
259
260        // Step c: P value as 4 little-endian bytes
261        hasher.update((p_value as u32).to_le_bytes());
262
263        // Step d: File ID (first element)
264        hasher.update(file_id);
265
266        let mut hash = hasher.finalize().to_vec();
267
268        // Step e: For revision 3, rehash 50 times
269        if revision >= 3 {
270            for _ in 0..50 {
271                hash = md5_hash(&hash[..key_len_bytes]);
272            }
273        }
274
275        hash[..key_len_bytes].to_vec()
276    }
277
278    /// Compute U value per Algorithm 3.4 (rev 2) or 3.5 (rev 3)
279    fn compute_u_value(&self, encryption_key: &[u8], file_id: &[u8], revision: usize) -> Vec<u8> {
280        if revision >= 3 {
281            // Algorithm 3.5 (revision 3)
282            let mut hasher = Md5::new();
283            hasher.update(PADDING);
284            hasher.update(file_id);
285            let hash = hasher.finalize().to_vec();
286
287            let mut result = rc4_encrypt(encryption_key, &hash);
288
289            for i in 1..=19 {
290                let derived_key: Vec<u8> = encryption_key.iter().map(|&b| b ^ (i as u8)).collect();
291                result = rc4_encrypt(&derived_key, &result);
292            }
293
294            // Pad to 32 bytes with arbitrary data
295            result.resize(32, 0);
296            result
297        } else {
298            // Algorithm 3.4 (revision 2)
299            rc4_encrypt(encryption_key, &PADDING)
300        }
301    }
302
303    // ------------------------------------------------------------------
304    // AES-256 implementation (PDF 2.0, /V 5 /R 6)
305    // ------------------------------------------------------------------
306
307    /// Compute AES-256 encryption dictionary (PDF 2.0 spec Algorithm 8/9/10)
308    fn compute_aes256_encryption_dict(&self) -> EncryptionDict {
309        let p_value = self.permissions.to_p_value();
310
311        // Generate random salts (8 bytes each) — use pseudo-random from hash of passwords
312        // In a production system, use a CSPRNG; here we derive deterministically for testing
313        let user_validation_salt = derive_salt(&self.user_password, b"user_val");
314        let user_key_salt = derive_salt(&self.user_password, b"user_key");
315        let owner_validation_salt = derive_salt(&self.owner_password, b"owner_val");
316        let owner_key_salt = derive_salt(&self.owner_password, b"owner_key");
317
318        // --- Algorithm 8: Compute U and UE ---
319        // U = SHA-256(user_password + user_validation_salt) || user_validation_salt || user_key_salt
320        // (total 48 bytes: 32 hash + 8 validation salt + 8 key salt)
321        let u_hash = sha256_hash_parts(&[self.user_password.as_bytes(), &user_validation_salt]);
322        let mut u_value = u_hash.clone();
323        u_value.extend_from_slice(&user_validation_salt);
324        u_value.extend_from_slice(&user_key_salt);
325        // u_value is 48 bytes
326
327        // File encryption key: 32 random bytes (derived deterministically)
328        let file_enc_key = derive_file_enc_key(&self.owner_password, &self.user_password);
329
330        // UE: encrypt file_enc_key with AES-256-CBC, key = SHA-256(user_password + user_key_salt)
331        let ue_key = sha256_hash_parts(&[self.user_password.as_bytes(), &user_key_salt]);
332        let ue_value = aes256_cbc_encrypt_no_iv(&file_enc_key, &ue_key);
333        // ue_value is 32 bytes (no IV — ECB-like, single block)
334
335        // --- Algorithm 9: Compute O and OE ---
336        // O = SHA-256(owner_password + owner_validation_salt + u_value[0..48]) || owner_validation_salt || owner_key_salt
337        let o_hash = sha256_hash_parts(&[
338            self.owner_password.as_bytes(),
339            &owner_validation_salt,
340            &u_value,
341        ]);
342        let mut o_value = o_hash;
343        o_value.extend_from_slice(&owner_validation_salt);
344        o_value.extend_from_slice(&owner_key_salt);
345        // o_value is 48 bytes
346
347        // OE: encrypt file_enc_key with AES-256-CBC, key = SHA-256(owner_password + owner_key_salt + u_value[0..48])
348        let oe_key =
349            sha256_hash_parts(&[self.owner_password.as_bytes(), &owner_key_salt, &u_value]);
350        let oe_value = aes256_cbc_encrypt_no_iv(&file_enc_key, &oe_key);
351
352        // --- Algorithm 10: Compute Perms ---
353        // Perms: encrypt 16-byte block with AES-256-ECB using file_enc_key
354        // Bytes: P as 4 LE bytes | 0xFF 0xFF 0xFF 0xFF | T/F for metadata | 'adb' | 4 random bytes
355        let mut perms_plain = [0u8; 16];
356        let p_bytes = (p_value as u32).to_le_bytes();
357        perms_plain[0..4].copy_from_slice(&p_bytes);
358        perms_plain[4] = 0xFF;
359        perms_plain[5] = 0xFF;
360        perms_plain[6] = 0xFF;
361        perms_plain[7] = 0xFF;
362        perms_plain[8] = b'T'; // encrypt metadata = true
363        perms_plain[9] = b'a';
364        perms_plain[10] = b'd';
365        perms_plain[11] = b'b';
366        // bytes 12-15: random (deterministic from key for reproducibility)
367        perms_plain[12] = file_enc_key[0];
368        perms_plain[13] = file_enc_key[1];
369        perms_plain[14] = file_enc_key[2];
370        perms_plain[15] = file_enc_key[3];
371
372        let perms_value = aes256_ecb_encrypt_block(&perms_plain, &file_enc_key);
373
374        EncryptionDict {
375            o_value,
376            u_value,
377            oe_value: Some(oe_value),
378            ue_value: Some(ue_value),
379            perms_value: Some(perms_value.to_vec()),
380            p_value,
381            encryption_key: file_enc_key,
382            key_length: 256,
383            revision: 6,
384            version: 5,
385            algorithm: EncryptionAlgorithm::Aes256,
386        }
387    }
388}
389
390// ---------------------------------------------------------------------------
391// EncryptionDict
392// ---------------------------------------------------------------------------
393
394/// Computed encryption dictionary and keys
395#[derive(Debug, Clone)]
396pub struct EncryptionDict {
397    /// The /O value (owner password hash, 32 bytes for RC4; 48 bytes for AES-256)
398    pub o_value: Vec<u8>,
399    /// The /U value (user password hash, 32 bytes for RC4; 48 bytes for AES-256)
400    pub u_value: Vec<u8>,
401    /// The /OE value (owner key, 32 bytes; AES-256 only)
402    pub oe_value: Option<Vec<u8>>,
403    /// The /UE value (user key, 32 bytes; AES-256 only)
404    pub ue_value: Option<Vec<u8>>,
405    /// The /Perms value (encrypted permissions, 16 bytes; AES-256 only)
406    pub perms_value: Option<Vec<u8>>,
407    /// The /P value (permission flags)
408    pub p_value: i32,
409    /// The encryption key (for encrypting streams/strings)
410    pub encryption_key: Vec<u8>,
411    /// Key length in bits
412    pub key_length: u32,
413    /// Revision number (2 for RC4-40, 3 for RC4-128, 6 for AES-256)
414    pub revision: u32,
415    /// Version number (1 for RC4-40, 2 for RC4-128, 5 for AES-256)
416    pub version: u32,
417    /// Encryption algorithm
418    pub algorithm: EncryptionAlgorithm,
419}
420
421impl EncryptionDict {
422    /// Encrypt a PDF stream or string for a specific object
423    ///
424    /// For RC4: derives per-object key (Algorithm 3.1) then RC4-encrypts.
425    /// For AES-256: encrypts with AES-256-CBC, prepending a 16-byte IV.
426    pub fn encrypt_data(&self, data: &[u8], obj_num: u32, gen_num: u16) -> Vec<u8> {
427        match self.algorithm {
428            EncryptionAlgorithm::Rc4128 => {
429                let key = self.derive_object_key(obj_num, gen_num);
430                rc4_encrypt(&key, data)
431            }
432            EncryptionAlgorithm::Aes256 => {
433                // AES-256-CBC with a deterministic IV (derived from key + obj_num for reproducibility)
434                encrypt_aes256_cbc(data, &self.encryption_key, obj_num)
435            }
436        }
437    }
438
439    /// Derive per-object encryption key (Algorithm 3.1) — RC4 only
440    fn derive_object_key(&self, obj_num: u32, gen_num: u16) -> Vec<u8> {
441        let mut hasher = Md5::new();
442        hasher.update(&self.encryption_key);
443        hasher.update(&obj_num.to_le_bytes()[..3]);
444        hasher.update(gen_num.to_le_bytes());
445        let hash = hasher.finalize();
446
447        // Key length is min(encryption_key.len() + 5, 16) bytes
448        let key_len = std::cmp::min(self.encryption_key.len() + 5, 16);
449        hash[..key_len].to_vec()
450    }
451
452    /// Generate the /Encrypt dictionary PDF object content
453    pub fn to_pdf_dict(&self, encrypt_obj_id: usize) -> String {
454        match self.algorithm {
455            EncryptionAlgorithm::Rc4128 => self.to_rc4_pdf_dict(encrypt_obj_id),
456            EncryptionAlgorithm::Aes256 => self.to_aes256_pdf_dict(encrypt_obj_id),
457        }
458    }
459
460    /// Generate RC4-128 /Encrypt dictionary
461    fn to_rc4_pdf_dict(&self, encrypt_obj_id: usize) -> String {
462        let mut dict = String::new();
463        dict.push_str(&format!("{} 0 obj\n", encrypt_obj_id));
464        dict.push_str("<<\n");
465        dict.push_str("/Filter /Standard\n");
466        dict.push_str(&format!("/V {}\n", self.version));
467        dict.push_str(&format!("/R {}\n", self.revision));
468        dict.push_str(&format!("/Length {}\n", self.key_length));
469        dict.push_str(&format!("/P {}\n", self.p_value));
470        dict.push_str(&format!("/O <{}>\n", hex_encode(&self.o_value)));
471        dict.push_str(&format!("/U <{}>\n", hex_encode(&self.u_value)));
472        dict.push_str(">>\n");
473        dict.push_str("endobj\n");
474        dict
475    }
476
477    /// Generate AES-256 /Encrypt dictionary (PDF 2.0 / PDF 1.7 extension)
478    fn to_aes256_pdf_dict(&self, encrypt_obj_id: usize) -> String {
479        let mut dict = String::new();
480        dict.push_str(&format!("{} 0 obj\n", encrypt_obj_id));
481        dict.push_str("<<\n");
482        dict.push_str("/Filter /Standard\n");
483        dict.push_str("/V 5\n");
484        dict.push_str("/R 6\n");
485        dict.push_str("/Length 256\n");
486        dict.push_str(&format!("/P {}\n", self.p_value));
487
488        // /O and /U are 48 bytes in AES-256 (32-byte hash + 8 validation salt + 8 key salt)
489        dict.push_str(&format!("/O <{}>\n", hex_encode(&self.o_value)));
490        dict.push_str(&format!("/U <{}>\n", hex_encode(&self.u_value)));
491
492        if let Some(ref oe) = self.oe_value {
493            dict.push_str(&format!("/OE <{}>\n", hex_encode(oe)));
494        }
495        if let Some(ref ue) = self.ue_value {
496            dict.push_str(&format!("/UE <{}>\n", hex_encode(ue)));
497        }
498        if let Some(ref perms) = self.perms_value {
499            dict.push_str(&format!("/Perms <{}>\n", hex_encode(perms)));
500        }
501
502        // Crypt filter for AES-256
503        dict.push_str("/CF <<\n");
504        dict.push_str("  /StdCF <<\n");
505        dict.push_str("    /AuthEvent /DocOpen\n");
506        dict.push_str("    /CFM /AESV3\n");
507        dict.push_str("    /Length 32\n");
508        dict.push_str("  >>\n");
509        dict.push_str(">>\n");
510        dict.push_str("/StmF /StdCF\n");
511        dict.push_str("/StrF /StdCF\n");
512        dict.push_str(">>\n");
513        dict.push_str("endobj\n");
514        dict
515    }
516
517    /// Encrypt data using AES-128-ECB for a single block
518    ///
519    /// Used for encrypting individual 16-byte blocks when needed.
520    #[allow(dead_code)]
521    pub fn aes128_encrypt_block(&self, data: &[u8; 16]) -> [u8; 16] {
522        let key = aes::cipher::generic_array::GenericArray::from_slice(&self.encryption_key[..16]);
523        let cipher = Aes128::new(key);
524        let mut block = aes::cipher::generic_array::GenericArray::clone_from_slice(data);
525        cipher.encrypt_block(&mut block);
526        block.into()
527    }
528
529    /// Encrypt data using AES-256-CBC with a random IV prepended to the output
530    ///
531    /// For AES-256 encryption of PDF streams and strings.
532    /// Returns IV (16 bytes) || ciphertext.
533    #[allow(dead_code)]
534    pub fn encrypt_aes256(data: &[u8], key: &[u8]) -> Vec<u8> {
535        // Generate a deterministic IV from key hash for testing
536        // In production, use a CSPRNG
537        let iv = sha256_hash_parts(&[key, data])[..16].to_vec();
538        let mut iv_arr = [0u8; 16];
539        iv_arr.copy_from_slice(&iv);
540
541        let padded = pkcs7_pad(data, 16);
542        let key_arr = aes::cipher::generic_array::GenericArray::from_slice(&key[..32]);
543        let iv_ga = aes::cipher::generic_array::GenericArray::from_slice(&iv_arr);
544
545        type Aes256Cbc = cbc::Encryptor<Aes256>;
546        let cipher = Aes256Cbc::new(key_arr, iv_ga);
547        let ciphertext =
548            cipher.encrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(&padded);
549
550        let mut result = iv_arr.to_vec();
551        result.extend_from_slice(&ciphertext);
552        result
553    }
554}
555
556// ---------------------------------------------------------------------------
557// PDF password padding string (RC4 only)
558// ---------------------------------------------------------------------------
559
560/// PDF password padding string (32 bytes) per PDF spec Algorithm 3.2
561const PADDING: [u8; 32] = [
562    0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
563    0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
564];
565
566// ---------------------------------------------------------------------------
567// Helper functions
568// ---------------------------------------------------------------------------
569
570/// Pad or truncate a password to 32 bytes using the PDF padding string (RC4)
571fn pad_password(password: &str) -> Vec<u8> {
572    let pwd_bytes = password.as_bytes();
573    let mut padded = Vec::with_capacity(32);
574
575    if pwd_bytes.len() >= 32 {
576        padded.extend_from_slice(&pwd_bytes[..32]);
577    } else {
578        padded.extend_from_slice(pwd_bytes);
579        let remaining = 32 - pwd_bytes.len();
580        padded.extend_from_slice(&PADDING[..remaining]);
581    }
582
583    padded
584}
585
586/// MD5 hash helper
587fn md5_hash(data: &[u8]) -> Vec<u8> {
588    let mut hasher = Md5::new();
589    hasher.update(data);
590    hasher.finalize().to_vec()
591}
592
593/// SHA-256 hash of multiple parts concatenated
594fn sha256_hash_parts(parts: &[&[u8]]) -> Vec<u8> {
595    let mut hasher = Sha256::new();
596    for part in parts {
597        hasher.update(part);
598    }
599    hasher.finalize().to_vec()
600}
601
602/// RC4 encryption (symmetric — same function for encrypt and decrypt)
603fn rc4_encrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
604    // Key Scheduling Algorithm (KSA)
605    let mut s: Vec<u8> = (0..=255).map(|i| i as u8).collect();
606    let mut j: usize = 0;
607    for i in 0..256 {
608        j = (j + s[i] as usize + key[i % key.len()] as usize) % 256;
609        s.swap(i, j);
610    }
611
612    // Pseudo-Random Generation Algorithm (PRGA)
613    let mut output = Vec::with_capacity(data.len());
614    let mut i: usize = 0;
615    j = 0;
616    for &byte in data {
617        i = (i + 1) % 256;
618        j = (j + s[i] as usize) % 256;
619        s.swap(i, j);
620        let k = s[(s[i] as usize + s[j] as usize) % 256];
621        output.push(byte ^ k);
622    }
623
624    output
625}
626
627/// AES-256-CBC encrypt with PKCS7 padding; IV prepended to output
628///
629/// Returns IV (16 bytes) || ciphertext.
630fn encrypt_aes256_cbc(data: &[u8], key: &[u8], obj_num: u32) -> Vec<u8> {
631    // Deterministic IV derived from key + obj_num (avoids need for rand crate)
632    let mut iv_src = [0u8; 36];
633    iv_src[..32].copy_from_slice(&key[..32]);
634    iv_src[32..36].copy_from_slice(&obj_num.to_le_bytes());
635    let iv_hash = sha256_hash_parts(&[&iv_src]);
636    let mut iv = [0u8; 16];
637    iv.copy_from_slice(&iv_hash[..16]);
638
639    let key_arr = aes::cipher::generic_array::GenericArray::from_slice(&key[..32]);
640    let iv_ga = aes::cipher::generic_array::GenericArray::from_slice(&iv);
641
642    type Aes256Cbc = cbc::Encryptor<Aes256>;
643    let cipher = Aes256Cbc::new(key_arr, iv_ga);
644    let ciphertext = cipher.encrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(data);
645
646    let mut result = iv.to_vec();
647    result.extend_from_slice(&ciphertext);
648    result
649}
650
651/// AES-256-CBC encrypt a single 32-byte key block (no IV) for /UE and /OE
652///
653/// Uses a zero IV (as per PDF 2.0 spec for key wrapping).
654fn aes256_cbc_encrypt_no_iv(data: &[u8], key: &[u8]) -> Vec<u8> {
655    let iv = [0u8; 16];
656    let key_arr = aes::cipher::generic_array::GenericArray::from_slice(&key[..32]);
657    let iv_ga = aes::cipher::generic_array::GenericArray::from_slice(&iv);
658
659    type Aes256Cbc = cbc::Encryptor<Aes256>;
660    let cipher = Aes256Cbc::new(key_arr, iv_ga);
661    cipher.encrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(data)
662}
663
664/// AES-256-ECB encrypt a single 16-byte block (for /Perms)
665fn aes256_ecb_encrypt_block(data: &[u8; 16], key: &[u8]) -> [u8; 16] {
666    let key_arr = aes::cipher::generic_array::GenericArray::from_slice(&key[..32]);
667    let cipher = Aes256::new(key_arr);
668    let mut block = aes::cipher::generic_array::GenericArray::clone_from_slice(data);
669    cipher.encrypt_block(&mut block);
670    block.into()
671}
672
673/// PKCS7 pad data to a multiple of block_size
674fn pkcs7_pad(data: &[u8], block_size: usize) -> Vec<u8> {
675    let pad_len = block_size - (data.len() % block_size);
676    let mut padded = data.to_vec();
677    for _ in 0..pad_len {
678        padded.push(pad_len as u8);
679    }
680    padded
681}
682
683/// Derive a deterministic 8-byte salt from a password and a tag
684fn derive_salt(password: &str, tag: &[u8]) -> [u8; 8] {
685    let hash = sha256_hash_parts(&[password.as_bytes(), tag]);
686    let mut salt = [0u8; 8];
687    salt.copy_from_slice(&hash[..8]);
688    salt
689}
690
691/// Derive a deterministic 32-byte file encryption key
692fn derive_file_enc_key(owner_pass: &str, user_pass: &str) -> Vec<u8> {
693    sha256_hash_parts(&[owner_pass.as_bytes(), user_pass.as_bytes(), b"file_enc_key"])
694}
695
696/// Encode bytes as hexadecimal string (uppercase)
697fn hex_encode(data: &[u8]) -> String {
698    data.iter().map(|b| format!("{:02X}", b)).collect()
699}
700
701/// Generate a PDF file ID (two identical 16-byte MD5 hashes)
702///
703/// In practice, the file ID should be based on file content,
704/// creation time, file size, etc. For simplicity, we generate
705/// from a seed string.
706pub fn generate_file_id(seed: &str) -> Vec<u8> {
707    md5_hash(seed.as_bytes())
708}
709
710// ---------------------------------------------------------------------------
711// Tests
712// ---------------------------------------------------------------------------
713
714#[cfg(test)]
715mod tests {
716    use super::*;
717
718    #[test]
719    fn test_pad_password_empty() {
720        let padded = pad_password("");
721        assert_eq!(padded.len(), 32);
722        assert_eq!(padded, PADDING.to_vec());
723    }
724
725    #[test]
726    fn test_pad_password_short() {
727        let padded = pad_password("test");
728        assert_eq!(padded.len(), 32);
729        assert_eq!(&padded[..4], b"test");
730        assert_eq!(&padded[4..], &PADDING[..28]);
731    }
732
733    #[test]
734    fn test_pad_password_long() {
735        let long_pwd = "a".repeat(40);
736        let padded = pad_password(&long_pwd);
737        assert_eq!(padded.len(), 32);
738        assert_eq!(padded, vec![b'a'; 32]);
739    }
740
741    #[test]
742    fn test_permissions_default_all_allowed() {
743        let perms = PdfPermissions::default();
744        let p = perms.to_p_value();
745        // All permission bits should be set
746        assert!(p & (1 << 2) != 0); // print
747        assert!(p & (1 << 3) != 0); // modify
748        assert!(p & (1 << 4) != 0); // copy
749        assert!(p & (1 << 5) != 0); // annotations
750    }
751
752    #[test]
753    fn test_permissions_restricted() {
754        let perms = PdfPermissions {
755            allow_print: false,
756            allow_copy: false,
757            allow_modify: false,
758            allow_annotations: false,
759            ..Default::default()
760        };
761        let p = perms.to_p_value();
762        assert!(p & (1 << 2) == 0); // no print
763        assert!(p & (1 << 3) == 0); // no modify
764        assert!(p & (1 << 4) == 0); // no copy
765        assert!(p & (1 << 5) == 0); // no annotations
766    }
767
768    #[test]
769    fn test_rc4_encrypt_decrypt_roundtrip() {
770        let key = b"testkey123456789";
771        let plaintext = b"Hello, World! This is a PDF encryption test.";
772        let encrypted = rc4_encrypt(key, plaintext);
773        let decrypted = rc4_encrypt(key, &encrypted);
774        assert_eq!(decrypted, plaintext);
775    }
776
777    #[test]
778    fn test_rc4_encryption_dict_computation() {
779        let security = PdfSecurity::new("owner", "user", PdfPermissions::default());
780        let file_id = generate_file_id("test-document");
781        let dict = security.compute_encryption_dict(&file_id);
782
783        assert_eq!(dict.o_value.len(), 32);
784        assert_eq!(dict.u_value.len(), 32);
785        assert_eq!(dict.key_length, 128);
786        assert_eq!(dict.revision, 3);
787        assert_eq!(dict.version, 2);
788        assert_eq!(dict.algorithm, EncryptionAlgorithm::Rc4128);
789    }
790
791    #[test]
792    fn test_rc4_object_encryption_roundtrip() {
793        let security = PdfSecurity::new("owner", "user", PdfPermissions::default());
794        let file_id = generate_file_id("test-doc");
795        let dict = security.compute_encryption_dict(&file_id);
796
797        let plaintext = b"This is a test stream content for PDF object.";
798        let encrypted = dict.encrypt_data(plaintext, 5, 0);
799        let decrypted = dict.encrypt_data(&encrypted, 5, 0);
800        assert_eq!(decrypted, plaintext);
801    }
802
803    #[test]
804    fn test_rc4_encryption_dict_pdf_output() {
805        let security = PdfSecurity::new("owner", "", PdfPermissions::default());
806        let file_id = generate_file_id("test");
807        let dict = security.compute_encryption_dict(&file_id);
808        let pdf_str = dict.to_pdf_dict(10);
809
810        assert!(pdf_str.contains("/Filter /Standard"));
811        assert!(pdf_str.contains("/V 2"));
812        assert!(pdf_str.contains("/R 3"));
813        assert!(pdf_str.contains("/Length 128"));
814        assert!(pdf_str.contains("/O <"));
815        assert!(pdf_str.contains("/U <"));
816    }
817
818    #[test]
819    fn test_aes256_encryption_dict_computation() {
820        let security =
821            PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
822        let dict = security.compute_encryption_dict(&[]);
823
824        // /O and /U are 48 bytes each (32-byte hash + 8 validation salt + 8 key salt)
825        assert_eq!(dict.o_value.len(), 48);
826        assert_eq!(dict.u_value.len(), 48);
827        assert_eq!(dict.key_length, 256);
828        assert_eq!(dict.revision, 6);
829        assert_eq!(dict.version, 5);
830        assert_eq!(dict.algorithm, EncryptionAlgorithm::Aes256);
831
832        // OE and UE should be present
833        assert!(dict.oe_value.is_some());
834        assert!(dict.ue_value.is_some());
835        assert!(dict.perms_value.is_some());
836
837        // /Perms is 16 bytes
838        assert_eq!(
839            dict.perms_value
840                .as_ref()
841                .expect("test: should succeed")
842                .len(),
843            16
844        );
845    }
846
847    #[test]
848    fn test_aes256_encryption_dict_pdf_output() {
849        let security =
850            PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
851        let dict = security.compute_encryption_dict(&[]);
852        let pdf_str = dict.to_pdf_dict(10);
853
854        assert!(pdf_str.contains("/Filter /Standard"));
855        assert!(pdf_str.contains("/V 5"));
856        assert!(pdf_str.contains("/R 6"));
857        assert!(pdf_str.contains("/Length 256"));
858        assert!(pdf_str.contains("/O <"));
859        assert!(pdf_str.contains("/U <"));
860        assert!(pdf_str.contains("/OE <"));
861        assert!(pdf_str.contains("/UE <"));
862        assert!(pdf_str.contains("/Perms <"));
863        assert!(pdf_str.contains("/CFM /AESV3"));
864        assert!(pdf_str.contains("/StmF /StdCF"));
865        assert!(pdf_str.contains("/StrF /StdCF"));
866    }
867
868    #[test]
869    fn test_aes256_stream_encryption() {
870        let security =
871            PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
872        let dict = security.compute_encryption_dict(&[]);
873
874        let plaintext = b"Sensitive PDF stream content.";
875        let encrypted = dict.encrypt_data(plaintext, 10, 0);
876
877        // Encrypted data should be longer (IV + padded ciphertext)
878        assert!(encrypted.len() > plaintext.len());
879        // Should not equal plaintext
880        assert_ne!(&encrypted[16..16 + plaintext.len()], plaintext.as_slice());
881    }
882
883    #[test]
884    fn test_aes256_deterministic() {
885        // Same inputs should produce same encryption output
886        let security =
887            PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
888        let dict = security.compute_encryption_dict(&[]);
889
890        let plaintext = b"Test data for AES-256.";
891        let enc1 = dict.encrypt_data(plaintext, 5, 0);
892        let enc2 = dict.encrypt_data(plaintext, 5, 0);
893        assert_eq!(enc1, enc2);
894    }
895
896    #[test]
897    fn test_hex_encode() {
898        assert_eq!(hex_encode(&[0xFF, 0x00, 0xAB]), "FF00AB");
899        assert_eq!(hex_encode(&[]), "");
900    }
901
902    #[test]
903    fn test_file_id_generation() {
904        let id1 = generate_file_id("doc1");
905        let id2 = generate_file_id("doc2");
906        assert_eq!(id1.len(), 16);
907        assert_ne!(id1, id2);
908    }
909
910    #[test]
911    fn test_encryption_algorithm_default() {
912        let algo = EncryptionAlgorithm::default();
913        assert_eq!(algo, EncryptionAlgorithm::Rc4128);
914    }
915
916    #[test]
917    fn test_pkcs7_pad() {
918        // 13 bytes padded to 16 → pad = 3 bytes of value 3
919        let data = b"Hello, World!";
920        let padded = pkcs7_pad(data, 16);
921        assert_eq!(padded.len(), 16);
922        assert_eq!(&padded[13..], &[3u8, 3, 3]);
923    }
924}
925
926#[cfg(test)]
927mod tests_extended {
928    use super::*;
929
930    #[test]
931    fn test_permissions_all_denied() {
932        let perms = PdfPermissions {
933            allow_print: false,
934            allow_modify: false,
935            allow_copy: false,
936            allow_annotations: false,
937            allow_fill_forms: false,
938            allow_accessibility: false,
939            allow_assemble: false,
940            allow_print_high_quality: false,
941        };
942        let p = perms.to_p_value();
943        // None of the user-controlled permission bits should be set
944        assert_eq!(p & (1 << 2), 0); // no print
945        assert_eq!(p & (1 << 3), 0); // no modify
946        assert_eq!(p & (1 << 4), 0); // no copy
947        assert_eq!(p & (1 << 5), 0); // no annotations
948        assert_eq!(p & (1 << 8), 0); // no fill forms
949        assert_eq!(p & (1 << 9), 0); // no accessibility
950        assert_eq!(p & (1 << 10), 0); // no assemble
951        assert_eq!(p & (1 << 11), 0); // no high-quality print
952    }
953
954    #[test]
955    fn test_permissions_only_print_allowed() {
956        let perms = PdfPermissions {
957            allow_print: true,
958            allow_modify: false,
959            allow_copy: false,
960            allow_annotations: false,
961            allow_fill_forms: false,
962            allow_accessibility: false,
963            allow_assemble: false,
964            allow_print_high_quality: false,
965        };
966        let p = perms.to_p_value();
967        assert_ne!(p & (1 << 2), 0); // print allowed
968        assert_eq!(p & (1 << 3), 0); // no modify
969        assert_eq!(p & (1 << 4), 0); // no copy
970    }
971
972    #[test]
973    fn test_pdf_security_new_stores_passwords() {
974        let perms = PdfPermissions::default();
975        let sec = PdfSecurity::new("owner_pw", "user_pw", perms);
976        assert_eq!(sec.owner_password, "owner_pw");
977        assert_eq!(sec.user_password, "user_pw");
978        assert_eq!(sec.key_length, 128);
979        assert_eq!(sec.algorithm, EncryptionAlgorithm::Rc4128);
980    }
981
982    #[test]
983    fn test_pdf_security_aes256_stores_correct_length() {
984        let perms = PdfPermissions::default();
985        let sec = PdfSecurity::new_aes256("owner", "user", perms);
986        assert_eq!(sec.key_length, 256);
987        assert_eq!(sec.algorithm, EncryptionAlgorithm::Aes256);
988    }
989
990    #[test]
991    fn test_rc4_different_objects_produce_different_ciphertext() {
992        let sec = PdfSecurity::new("owner", "user", PdfPermissions::default());
993        let file_id = generate_file_id("doc");
994        let dict = sec.compute_encryption_dict(&file_id);
995
996        let plaintext = b"same plaintext for both objects";
997        let enc_obj1 = dict.encrypt_data(plaintext, 1, 0);
998        let enc_obj2 = dict.encrypt_data(plaintext, 2, 0);
999        // Different objects → different derived keys → different ciphertext
1000        assert_ne!(enc_obj1, enc_obj2);
1001    }
1002
1003    #[test]
1004    fn test_aes256_different_objects_produce_different_ciphertext() {
1005        let sec = PdfSecurity::new_aes256("owner", "user", PdfPermissions::default());
1006        let dict = sec.compute_encryption_dict(&[]);
1007
1008        let plaintext = b"same plaintext";
1009        let enc_obj1 = dict.encrypt_data(plaintext, 1, 0);
1010        let enc_obj2 = dict.encrypt_data(plaintext, 2, 0);
1011        assert_ne!(enc_obj1, enc_obj2);
1012    }
1013
1014    #[test]
1015    fn test_rc4_empty_plaintext() {
1016        let sec = PdfSecurity::new("owner", "user", PdfPermissions::default());
1017        let file_id = generate_file_id("doc");
1018        let dict = sec.compute_encryption_dict(&file_id);
1019
1020        let encrypted = dict.encrypt_data(b"", 1, 0);
1021        // RC4 of empty input is empty
1022        assert!(encrypted.is_empty());
1023    }
1024
1025    #[test]
1026    fn test_generate_file_id_is_16_bytes() {
1027        let id = generate_file_id("test");
1028        assert_eq!(id.len(), 16);
1029    }
1030
1031    #[test]
1032    fn test_generate_file_id_different_seeds_differ() {
1033        let a = generate_file_id("seed_a");
1034        let b = generate_file_id("seed_b");
1035        assert_ne!(a, b);
1036    }
1037
1038    #[test]
1039    fn test_hex_encode_all_bytes() {
1040        let data: Vec<u8> = (0..=15).collect();
1041        let hex = hex_encode(&data);
1042        assert_eq!(hex, "000102030405060708090A0B0C0D0E0F");
1043    }
1044
1045    #[test]
1046    fn test_pkcs7_pad_exact_block() {
1047        // Exactly 16 bytes: padding block of 16 bytes with value 16 is added
1048        let data = b"1234567890123456";
1049        let padded = pkcs7_pad(data, 16);
1050        assert_eq!(padded.len(), 32);
1051        assert_eq!(&padded[16..], &[16u8; 16]);
1052    }
1053
1054    #[test]
1055    fn test_encryption_dict_clone() {
1056        let sec = PdfSecurity::new("o", "u", PdfPermissions::default());
1057        let file_id = generate_file_id("clone");
1058        let dict = sec.compute_encryption_dict(&file_id);
1059        let dict2 = dict.clone();
1060        assert_eq!(dict.key_length, dict2.key_length);
1061        assert_eq!(dict.algorithm, dict2.algorithm);
1062    }
1063
1064    #[test]
1065    fn test_encryption_algorithm_debug() {
1066        let rc4 = EncryptionAlgorithm::Rc4128;
1067        let aes = EncryptionAlgorithm::Aes256;
1068        assert!(format!("{:?}", rc4).contains("Rc4"));
1069        assert!(format!("{:?}", aes).contains("Aes"));
1070    }
1071}