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::{BlockCipherEncrypt, KeyInit};
12use aes::{Aes128, Aes256};
13use cbc::cipher::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 cipher = Aes128::new_from_slice(&self.encryption_key[..16])
523            .expect("AES-128 key length is 16 bytes");
524        let mut block = aes::Block::try_from(data.as_slice()).expect("block is exactly 16 bytes");
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        type Aes256Cbc = cbc::Encryptor<Aes256>;
542        let cipher =
543            Aes256Cbc::new_from_slices(&key[..32], &iv_arr).expect("AES-256 key/IV lengths valid");
544        let ciphertext = aes256_cbc_encrypt_with_pkcs7(cipher, data);
545
546        let mut result = iv_arr.to_vec();
547        result.extend_from_slice(&ciphertext);
548        result
549    }
550}
551
552// ---------------------------------------------------------------------------
553// PDF password padding string (RC4 only)
554// ---------------------------------------------------------------------------
555
556/// PDF password padding string (32 bytes) per PDF spec Algorithm 3.2
557const PADDING: [u8; 32] = [
558    0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
559    0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
560];
561
562// ---------------------------------------------------------------------------
563// Helper functions
564// ---------------------------------------------------------------------------
565
566/// Pad or truncate a password to 32 bytes using the PDF padding string (RC4)
567fn pad_password(password: &str) -> Vec<u8> {
568    let pwd_bytes = password.as_bytes();
569    let mut padded = Vec::with_capacity(32);
570
571    if pwd_bytes.len() >= 32 {
572        padded.extend_from_slice(&pwd_bytes[..32]);
573    } else {
574        padded.extend_from_slice(pwd_bytes);
575        let remaining = 32 - pwd_bytes.len();
576        padded.extend_from_slice(&PADDING[..remaining]);
577    }
578
579    padded
580}
581
582/// MD5 hash helper
583fn md5_hash(data: &[u8]) -> Vec<u8> {
584    let mut hasher = Md5::new();
585    hasher.update(data);
586    hasher.finalize().to_vec()
587}
588
589/// SHA-256 hash of multiple parts concatenated
590fn sha256_hash_parts(parts: &[&[u8]]) -> Vec<u8> {
591    let mut hasher = Sha256::new();
592    for part in parts {
593        hasher.update(part);
594    }
595    hasher.finalize().to_vec()
596}
597
598/// RC4 encryption (symmetric — same function for encrypt and decrypt)
599fn rc4_encrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
600    // Key Scheduling Algorithm (KSA)
601    let mut s: Vec<u8> = (0..=255).map(|i| i as u8).collect();
602    let mut j: usize = 0;
603    for i in 0..256 {
604        j = (j + s[i] as usize + key[i % key.len()] as usize) % 256;
605        s.swap(i, j);
606    }
607
608    // Pseudo-Random Generation Algorithm (PRGA)
609    let mut output = Vec::with_capacity(data.len());
610    let mut i: usize = 0;
611    j = 0;
612    for &byte in data {
613        i = (i + 1) % 256;
614        j = (j + s[i] as usize) % 256;
615        s.swap(i, j);
616        let k = s[(s[i] as usize + s[j] as usize) % 256];
617        output.push(byte ^ k);
618    }
619
620    output
621}
622
623/// AES-256-CBC encrypt with PKCS7 padding; IV prepended to output
624///
625/// Returns IV (16 bytes) || ciphertext.
626fn encrypt_aes256_cbc(data: &[u8], key: &[u8], obj_num: u32) -> Vec<u8> {
627    // Deterministic IV derived from key + obj_num (avoids need for rand crate)
628    let mut iv_src = [0u8; 36];
629    iv_src[..32].copy_from_slice(&key[..32]);
630    iv_src[32..36].copy_from_slice(&obj_num.to_le_bytes());
631    let iv_hash = sha256_hash_parts(&[&iv_src]);
632    let mut iv = [0u8; 16];
633    iv.copy_from_slice(&iv_hash[..16]);
634
635    type Aes256Cbc = cbc::Encryptor<Aes256>;
636    let cipher = Aes256Cbc::new_from_slices(&key[..32], &iv).expect("AES-256 key/IV lengths valid");
637    let ciphertext = aes256_cbc_encrypt_with_pkcs7(cipher, data);
638
639    let mut result = iv.to_vec();
640    result.extend_from_slice(&ciphertext);
641    result
642}
643
644/// AES-256-CBC encrypt a single 32-byte key block (no IV) for /UE and /OE
645///
646/// Uses a zero IV (as per PDF 2.0 spec for key wrapping).
647fn aes256_cbc_encrypt_no_iv(data: &[u8], key: &[u8]) -> Vec<u8> {
648    let iv = [0u8; 16];
649    type Aes256Cbc = cbc::Encryptor<Aes256>;
650    let cipher = Aes256Cbc::new_from_slices(&key[..32], &iv).expect("AES-256 key/IV lengths valid");
651    aes256_cbc_encrypt_with_pkcs7(cipher, data)
652}
653
654/// AES-256-ECB encrypt a single 16-byte block (for /Perms)
655fn aes256_ecb_encrypt_block(data: &[u8; 16], key: &[u8]) -> [u8; 16] {
656    let cipher = Aes256::new_from_slice(&key[..32]).expect("AES-256 key length is 32 bytes");
657    let mut block = aes::Block::try_from(data.as_slice()).expect("block is exactly 16 bytes");
658    cipher.encrypt_block(&mut block);
659    block.into()
660}
661
662/// PKCS7 pad data to a multiple of block_size
663fn pkcs7_pad(data: &[u8], block_size: usize) -> Vec<u8> {
664    let pad_len = block_size - (data.len() % block_size);
665    let mut padded = data.to_vec();
666    for _ in 0..pad_len {
667        padded.push(pad_len as u8);
668    }
669    padded
670}
671
672/// AES-256-CBC encrypt `data` with PKCS7 padding using the given encryptor.
673///
674/// This avoids the `cipher/alloc` feature requirement by manually padding
675/// and encrypting 16-byte blocks.
676fn aes256_cbc_encrypt_with_pkcs7(mut cipher: cbc::Encryptor<Aes256>, data: &[u8]) -> Vec<u8> {
677    use cbc::cipher::BlockModeEncrypt;
678    let padded = pkcs7_pad(data, 16);
679    // Process blocks in-place
680    let mut out = padded;
681    let block_count = out.len() / 16;
682    for i in 0..block_count {
683        let start = i * 16;
684        let end = start + 16;
685        let mut block = aes::Block::try_from(&out[start..end]).expect("block is exactly 16 bytes");
686        cipher.encrypt_block(&mut block);
687        out[start..end].copy_from_slice(&block);
688    }
689    out
690}
691
692/// Derive a deterministic 8-byte salt from a password and a tag
693fn derive_salt(password: &str, tag: &[u8]) -> [u8; 8] {
694    let hash = sha256_hash_parts(&[password.as_bytes(), tag]);
695    let mut salt = [0u8; 8];
696    salt.copy_from_slice(&hash[..8]);
697    salt
698}
699
700/// Derive a deterministic 32-byte file encryption key
701fn derive_file_enc_key(owner_pass: &str, user_pass: &str) -> Vec<u8> {
702    sha256_hash_parts(&[owner_pass.as_bytes(), user_pass.as_bytes(), b"file_enc_key"])
703}
704
705/// Encode bytes as hexadecimal string (uppercase)
706fn hex_encode(data: &[u8]) -> String {
707    data.iter().map(|b| format!("{:02X}", b)).collect()
708}
709
710/// Generate a PDF file ID (two identical 16-byte MD5 hashes)
711///
712/// In practice, the file ID should be based on file content,
713/// creation time, file size, etc. For simplicity, we generate
714/// from a seed string.
715pub fn generate_file_id(seed: &str) -> Vec<u8> {
716    md5_hash(seed.as_bytes())
717}
718
719// ---------------------------------------------------------------------------
720// Tests
721// ---------------------------------------------------------------------------
722
723#[cfg(test)]
724mod tests {
725    use super::*;
726
727    #[test]
728    fn test_pad_password_empty() {
729        let padded = pad_password("");
730        assert_eq!(padded.len(), 32);
731        assert_eq!(padded, PADDING.to_vec());
732    }
733
734    #[test]
735    fn test_pad_password_short() {
736        let padded = pad_password("test");
737        assert_eq!(padded.len(), 32);
738        assert_eq!(&padded[..4], b"test");
739        assert_eq!(&padded[4..], &PADDING[..28]);
740    }
741
742    #[test]
743    fn test_pad_password_long() {
744        let long_pwd = "a".repeat(40);
745        let padded = pad_password(&long_pwd);
746        assert_eq!(padded.len(), 32);
747        assert_eq!(padded, vec![b'a'; 32]);
748    }
749
750    #[test]
751    fn test_permissions_default_all_allowed() {
752        let perms = PdfPermissions::default();
753        let p = perms.to_p_value();
754        // All permission bits should be set
755        assert!(p & (1 << 2) != 0); // print
756        assert!(p & (1 << 3) != 0); // modify
757        assert!(p & (1 << 4) != 0); // copy
758        assert!(p & (1 << 5) != 0); // annotations
759    }
760
761    #[test]
762    fn test_permissions_restricted() {
763        let perms = PdfPermissions {
764            allow_print: false,
765            allow_copy: false,
766            allow_modify: false,
767            allow_annotations: false,
768            ..Default::default()
769        };
770        let p = perms.to_p_value();
771        assert!(p & (1 << 2) == 0); // no print
772        assert!(p & (1 << 3) == 0); // no modify
773        assert!(p & (1 << 4) == 0); // no copy
774        assert!(p & (1 << 5) == 0); // no annotations
775    }
776
777    #[test]
778    fn test_rc4_encrypt_decrypt_roundtrip() {
779        let key = b"testkey123456789";
780        let plaintext = b"Hello, World! This is a PDF encryption test.";
781        let encrypted = rc4_encrypt(key, plaintext);
782        let decrypted = rc4_encrypt(key, &encrypted);
783        assert_eq!(decrypted, plaintext);
784    }
785
786    #[test]
787    fn test_rc4_encryption_dict_computation() {
788        let security = PdfSecurity::new("owner", "user", PdfPermissions::default());
789        let file_id = generate_file_id("test-document");
790        let dict = security.compute_encryption_dict(&file_id);
791
792        assert_eq!(dict.o_value.len(), 32);
793        assert_eq!(dict.u_value.len(), 32);
794        assert_eq!(dict.key_length, 128);
795        assert_eq!(dict.revision, 3);
796        assert_eq!(dict.version, 2);
797        assert_eq!(dict.algorithm, EncryptionAlgorithm::Rc4128);
798    }
799
800    #[test]
801    fn test_rc4_object_encryption_roundtrip() {
802        let security = PdfSecurity::new("owner", "user", PdfPermissions::default());
803        let file_id = generate_file_id("test-doc");
804        let dict = security.compute_encryption_dict(&file_id);
805
806        let plaintext = b"This is a test stream content for PDF object.";
807        let encrypted = dict.encrypt_data(plaintext, 5, 0);
808        let decrypted = dict.encrypt_data(&encrypted, 5, 0);
809        assert_eq!(decrypted, plaintext);
810    }
811
812    #[test]
813    fn test_rc4_encryption_dict_pdf_output() {
814        let security = PdfSecurity::new("owner", "", PdfPermissions::default());
815        let file_id = generate_file_id("test");
816        let dict = security.compute_encryption_dict(&file_id);
817        let pdf_str = dict.to_pdf_dict(10);
818
819        assert!(pdf_str.contains("/Filter /Standard"));
820        assert!(pdf_str.contains("/V 2"));
821        assert!(pdf_str.contains("/R 3"));
822        assert!(pdf_str.contains("/Length 128"));
823        assert!(pdf_str.contains("/O <"));
824        assert!(pdf_str.contains("/U <"));
825    }
826
827    #[test]
828    fn test_aes256_encryption_dict_computation() {
829        let security =
830            PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
831        let dict = security.compute_encryption_dict(&[]);
832
833        // /O and /U are 48 bytes each (32-byte hash + 8 validation salt + 8 key salt)
834        assert_eq!(dict.o_value.len(), 48);
835        assert_eq!(dict.u_value.len(), 48);
836        assert_eq!(dict.key_length, 256);
837        assert_eq!(dict.revision, 6);
838        assert_eq!(dict.version, 5);
839        assert_eq!(dict.algorithm, EncryptionAlgorithm::Aes256);
840
841        // OE and UE should be present
842        assert!(dict.oe_value.is_some());
843        assert!(dict.ue_value.is_some());
844        assert!(dict.perms_value.is_some());
845
846        // /Perms is 16 bytes
847        assert_eq!(
848            dict.perms_value
849                .as_ref()
850                .expect("test: should succeed")
851                .len(),
852            16
853        );
854    }
855
856    #[test]
857    fn test_aes256_encryption_dict_pdf_output() {
858        let security =
859            PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
860        let dict = security.compute_encryption_dict(&[]);
861        let pdf_str = dict.to_pdf_dict(10);
862
863        assert!(pdf_str.contains("/Filter /Standard"));
864        assert!(pdf_str.contains("/V 5"));
865        assert!(pdf_str.contains("/R 6"));
866        assert!(pdf_str.contains("/Length 256"));
867        assert!(pdf_str.contains("/O <"));
868        assert!(pdf_str.contains("/U <"));
869        assert!(pdf_str.contains("/OE <"));
870        assert!(pdf_str.contains("/UE <"));
871        assert!(pdf_str.contains("/Perms <"));
872        assert!(pdf_str.contains("/CFM /AESV3"));
873        assert!(pdf_str.contains("/StmF /StdCF"));
874        assert!(pdf_str.contains("/StrF /StdCF"));
875    }
876
877    #[test]
878    fn test_aes256_stream_encryption() {
879        let security =
880            PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
881        let dict = security.compute_encryption_dict(&[]);
882
883        let plaintext = b"Sensitive PDF stream content.";
884        let encrypted = dict.encrypt_data(plaintext, 10, 0);
885
886        // Encrypted data should be longer (IV + padded ciphertext)
887        assert!(encrypted.len() > plaintext.len());
888        // Should not equal plaintext
889        assert_ne!(&encrypted[16..16 + plaintext.len()], plaintext.as_slice());
890    }
891
892    #[test]
893    fn test_aes256_deterministic() {
894        // Same inputs should produce same encryption output
895        let security =
896            PdfSecurity::new_aes256("owner_pass", "user_pass", PdfPermissions::default());
897        let dict = security.compute_encryption_dict(&[]);
898
899        let plaintext = b"Test data for AES-256.";
900        let enc1 = dict.encrypt_data(plaintext, 5, 0);
901        let enc2 = dict.encrypt_data(plaintext, 5, 0);
902        assert_eq!(enc1, enc2);
903    }
904
905    #[test]
906    fn test_hex_encode() {
907        assert_eq!(hex_encode(&[0xFF, 0x00, 0xAB]), "FF00AB");
908        assert_eq!(hex_encode(&[]), "");
909    }
910
911    #[test]
912    fn test_file_id_generation() {
913        let id1 = generate_file_id("doc1");
914        let id2 = generate_file_id("doc2");
915        assert_eq!(id1.len(), 16);
916        assert_ne!(id1, id2);
917    }
918
919    #[test]
920    fn test_encryption_algorithm_default() {
921        let algo = EncryptionAlgorithm::default();
922        assert_eq!(algo, EncryptionAlgorithm::Rc4128);
923    }
924
925    #[test]
926    fn test_pkcs7_pad() {
927        // 13 bytes padded to 16 → pad = 3 bytes of value 3
928        let data = b"Hello, World!";
929        let padded = pkcs7_pad(data, 16);
930        assert_eq!(padded.len(), 16);
931        assert_eq!(&padded[13..], &[3u8, 3, 3]);
932    }
933}
934
935#[cfg(test)]
936mod tests_extended {
937    use super::*;
938
939    #[test]
940    fn test_permissions_all_denied() {
941        let perms = PdfPermissions {
942            allow_print: false,
943            allow_modify: false,
944            allow_copy: false,
945            allow_annotations: false,
946            allow_fill_forms: false,
947            allow_accessibility: false,
948            allow_assemble: false,
949            allow_print_high_quality: false,
950        };
951        let p = perms.to_p_value();
952        // None of the user-controlled permission bits should be set
953        assert_eq!(p & (1 << 2), 0); // no print
954        assert_eq!(p & (1 << 3), 0); // no modify
955        assert_eq!(p & (1 << 4), 0); // no copy
956        assert_eq!(p & (1 << 5), 0); // no annotations
957        assert_eq!(p & (1 << 8), 0); // no fill forms
958        assert_eq!(p & (1 << 9), 0); // no accessibility
959        assert_eq!(p & (1 << 10), 0); // no assemble
960        assert_eq!(p & (1 << 11), 0); // no high-quality print
961    }
962
963    #[test]
964    fn test_permissions_only_print_allowed() {
965        let perms = PdfPermissions {
966            allow_print: true,
967            allow_modify: false,
968            allow_copy: false,
969            allow_annotations: false,
970            allow_fill_forms: false,
971            allow_accessibility: false,
972            allow_assemble: false,
973            allow_print_high_quality: false,
974        };
975        let p = perms.to_p_value();
976        assert_ne!(p & (1 << 2), 0); // print allowed
977        assert_eq!(p & (1 << 3), 0); // no modify
978        assert_eq!(p & (1 << 4), 0); // no copy
979    }
980
981    #[test]
982    fn test_pdf_security_new_stores_passwords() {
983        let perms = PdfPermissions::default();
984        let sec = PdfSecurity::new("owner_pw", "user_pw", perms);
985        assert_eq!(sec.owner_password, "owner_pw");
986        assert_eq!(sec.user_password, "user_pw");
987        assert_eq!(sec.key_length, 128);
988        assert_eq!(sec.algorithm, EncryptionAlgorithm::Rc4128);
989    }
990
991    #[test]
992    fn test_pdf_security_aes256_stores_correct_length() {
993        let perms = PdfPermissions::default();
994        let sec = PdfSecurity::new_aes256("owner", "user", perms);
995        assert_eq!(sec.key_length, 256);
996        assert_eq!(sec.algorithm, EncryptionAlgorithm::Aes256);
997    }
998
999    #[test]
1000    fn test_rc4_different_objects_produce_different_ciphertext() {
1001        let sec = PdfSecurity::new("owner", "user", PdfPermissions::default());
1002        let file_id = generate_file_id("doc");
1003        let dict = sec.compute_encryption_dict(&file_id);
1004
1005        let plaintext = b"same plaintext for both objects";
1006        let enc_obj1 = dict.encrypt_data(plaintext, 1, 0);
1007        let enc_obj2 = dict.encrypt_data(plaintext, 2, 0);
1008        // Different objects → different derived keys → different ciphertext
1009        assert_ne!(enc_obj1, enc_obj2);
1010    }
1011
1012    #[test]
1013    fn test_aes256_different_objects_produce_different_ciphertext() {
1014        let sec = PdfSecurity::new_aes256("owner", "user", PdfPermissions::default());
1015        let dict = sec.compute_encryption_dict(&[]);
1016
1017        let plaintext = b"same plaintext";
1018        let enc_obj1 = dict.encrypt_data(plaintext, 1, 0);
1019        let enc_obj2 = dict.encrypt_data(plaintext, 2, 0);
1020        assert_ne!(enc_obj1, enc_obj2);
1021    }
1022
1023    #[test]
1024    fn test_rc4_empty_plaintext() {
1025        let sec = PdfSecurity::new("owner", "user", PdfPermissions::default());
1026        let file_id = generate_file_id("doc");
1027        let dict = sec.compute_encryption_dict(&file_id);
1028
1029        let encrypted = dict.encrypt_data(b"", 1, 0);
1030        // RC4 of empty input is empty
1031        assert!(encrypted.is_empty());
1032    }
1033
1034    #[test]
1035    fn test_generate_file_id_is_16_bytes() {
1036        let id = generate_file_id("test");
1037        assert_eq!(id.len(), 16);
1038    }
1039
1040    #[test]
1041    fn test_generate_file_id_different_seeds_differ() {
1042        let a = generate_file_id("seed_a");
1043        let b = generate_file_id("seed_b");
1044        assert_ne!(a, b);
1045    }
1046
1047    #[test]
1048    fn test_hex_encode_all_bytes() {
1049        let data: Vec<u8> = (0..=15).collect();
1050        let hex = hex_encode(&data);
1051        assert_eq!(hex, "000102030405060708090A0B0C0D0E0F");
1052    }
1053
1054    #[test]
1055    fn test_pkcs7_pad_exact_block() {
1056        // Exactly 16 bytes: padding block of 16 bytes with value 16 is added
1057        let data = b"1234567890123456";
1058        let padded = pkcs7_pad(data, 16);
1059        assert_eq!(padded.len(), 32);
1060        assert_eq!(&padded[16..], &[16u8; 16]);
1061    }
1062
1063    #[test]
1064    fn test_encryption_dict_clone() {
1065        let sec = PdfSecurity::new("o", "u", PdfPermissions::default());
1066        let file_id = generate_file_id("clone");
1067        let dict = sec.compute_encryption_dict(&file_id);
1068        let dict2 = dict.clone();
1069        assert_eq!(dict.key_length, dict2.key_length);
1070        assert_eq!(dict.algorithm, dict2.algorithm);
1071    }
1072
1073    #[test]
1074    fn test_encryption_algorithm_debug() {
1075        let rc4 = EncryptionAlgorithm::Rc4128;
1076        let aes = EncryptionAlgorithm::Aes256;
1077        assert!(format!("{:?}", rc4).contains("Rc4"));
1078        assert!(format!("{:?}", aes).contains("Aes"));
1079    }
1080}