Skip to main content

pdfboss_core/
crypt.rs

1//! Decryption for the Standard security handler (ISO 32000 §7.6), opened
2//! with the user or the owner password (the empty user password opens
3//! transparently).
4//!
5//! Handles RC4 (`/V` 1–2, `/R` 2–3, 40–128-bit), AESV2 (`/V` 4, 128-bit
6//! AES-CBC) and AESV3 (`/V` 5, `/R` 5–6, 256-bit AES-CBC). Documents whose
7//! password the caller does not supply are reported as encrypted. The
8//! primitives — MD5, RC4, AES and the SHA-2 family — are implemented here from
9//! their published specifications so the crate needs no cryptographic
10//! dependency.
11//!
12//! This is low-level API: [`crate::Document`] applies it automatically when
13//! opening an encrypted file, and most callers never name [`Decryptor`]
14//! directly. It is public for code that drives object reads itself, as the
15//! asynchronous API does.
16//!
17//! [`Encryptor`] is the write side, AES-256 (`/V` 5, `/R` 6) only: it builds
18//! the complete `/Encrypt` dictionary and encrypts an object's strings and
19//! stream data in place, the exact inverse of [`Decryptor::decrypt_object`].
20
21use crate::object::{Dict, Name, Object};
22
23/// Password padding string (ISO 32000 §7.6.3.3, Algorithm 2, step (a)).
24const PAD: [u8; 32] = [
25    0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
26    0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
27];
28
29/// Which cipher a configured [`Decryptor`] applies to strings and streams.
30#[derive(Clone, Copy, PartialEq)]
31enum Cipher {
32    /// RC4 stream cipher, per-object key (V1/V2, and V4 with `/CFM /V2`).
33    Rc4,
34    /// AES-128-CBC, per-object key with the `sAlT` suffix (V4, `/CFM /AESV2`).
35    Aesv2,
36    /// AES-256-CBC, the file key applied directly (V5, `/CFM /AESV3`).
37    Aesv3,
38}
39
40/// A configured Standard-handler decryptor for an opened document.
41#[derive(Clone)]
42pub struct Decryptor {
43    /// The file key (`n` bytes for RC4/AESV2, 32 for AESV3).
44    key: Vec<u8>,
45    cipher: Cipher,
46    /// The `/Encrypt` dictionary's `/EncryptMetadata` value (default true
47    /// when absent): when false, a stream whose own dictionary says
48    /// `/Type /Metadata` was stored in plaintext and must not be decrypted.
49    encrypt_metadata: bool,
50}
51
52/// The `/Encrypt` dictionary's `/EncryptMetadata` flag, true when absent
53/// (ISO 32000-2 §7.6.4.2, Table 20).
54fn encrypt_metadata_flag(enc: &Dict) -> bool {
55    enc.get("EncryptMetadata")
56        .and_then(Object::as_bool)
57        .unwrap_or(true)
58}
59
60impl Decryptor {
61    /// Builds a decryptor from the resolved `/Encrypt` dictionary and the first
62    /// `/ID` element, assuming the empty user password. Returns `None` when the
63    /// handler or its parameters are unsupported, or the empty password does
64    /// not open the file (so the caller can report it as unsupported).
65    ///
66    /// This is low-level: [`crate::Document`] configures decryption itself, and
67    /// a caller only reaches for this when driving object reads directly — as
68    /// the asynchronous API does.
69    ///
70    /// ```
71    /// use pdfboss_core::{Decryptor, Dict};
72    ///
73    /// // An empty dictionary names no handler, so there is nothing to build.
74    /// assert!(Decryptor::from_standard(&Dict::default(), &[]).is_none());
75    /// ```
76    pub fn from_standard(enc: &Dict, id0: &[u8]) -> Option<Decryptor> {
77        Decryptor::from_standard_with_password(enc, id0, b"")
78    }
79
80    /// [`Decryptor::from_standard_with_password`] for a text password: the
81    /// UTF-8 bytes are tried first and, when they differ and every char
82    /// fits, the Latin-1 bytes as well — the legacy revisions hash raw
83    /// bytes without naming an encoding, and real files use both.
84    pub fn from_standard_with_password_str(
85        enc: &Dict,
86        id0: &[u8],
87        password: &str,
88    ) -> Option<Decryptor> {
89        let dec = Decryptor::from_standard_with_password(enc, id0, password.as_bytes());
90        if dec.is_some() || password.is_ascii() || password.chars().any(|c| (c as u32) > 255) {
91            return dec;
92        }
93        let latin1: Vec<u8> = password.chars().map(|c| c as u8).collect();
94        Decryptor::from_standard_with_password(enc, id0, &latin1)
95    }
96
97    /// [`Decryptor::from_standard`] with a caller-supplied password, tried
98    /// first as the user password and then as the owner password (which
99    /// recovers the user-level key, ISO 32000 §7.6.3.4 Algorithm 7 for the
100    /// RC4/AES-128 revisions, §7.6.4.3.3 for AES-256). `None` when the
101    /// password opens nothing.
102    pub fn from_standard_with_password(
103        enc: &Dict,
104        id0: &[u8],
105        password: &[u8],
106    ) -> Option<Decryptor> {
107        if enc.get_name("Filter").map(|n| n.0.as_str()) != Some("Standard") {
108            return None;
109        }
110        let v = enc.get_int("V").unwrap_or(0);
111        let r = enc.get_int("R").unwrap_or(0);
112        let encrypt_metadata = encrypt_metadata_flag(enc);
113        match (v, r) {
114            // RC4: V1 (40-bit) and V2 (up to 128-bit).
115            (1 | 2, 2 | 3) => {
116                let n = if v == 1 {
117                    5
118                } else {
119                    (enc.get_int("Length").unwrap_or(40) / 8).clamp(5, 16) as usize
120                };
121                let key = rc4_family_key(enc, id0, r, n, password)?;
122                Some(Decryptor {
123                    key,
124                    cipher: Cipher::Rc4,
125                    encrypt_metadata,
126                })
127            }
128            // V4: 128-bit key, cipher chosen by the standard crypt filter.
129            (4, 4) => {
130                let key = rc4_family_key(enc, id0, r, 16, password)?;
131                let cipher = match crypt_filter_method(enc)?.as_str() {
132                    "AESV2" => Cipher::Aesv2,
133                    "V2" => Cipher::Rc4,
134                    _ => return None, // Identity or unknown
135                };
136                Some(Decryptor {
137                    key,
138                    cipher,
139                    encrypt_metadata,
140                })
141            }
142            // V5: AES-256 with SHA-2-based key derivation.
143            (5, 5 | 6) => aesv3_key(enc, r, password).map(|key| Decryptor {
144                key,
145                cipher: Cipher::Aesv3,
146                encrypt_metadata,
147            }),
148            _ => None,
149        }
150    }
151
152    /// Decrypts one indirect object's strings and stream data in place. Objects
153    /// extracted from object streams are already plaintext and must not be
154    /// passed here.
155    ///
156    /// Low-level, like [`Decryptor::from_standard`]: [`crate::Document`] applies
157    /// this itself as it loads objects, and a caller only reaches for it when
158    /// driving object reads directly — as the asynchronous API does.
159    ///
160    /// ```
161    /// use pdfboss_core::{Decryptor, Object};
162    ///
163    /// // Reachable from outside the crate; exercising it needs an encrypted
164    /// // document, so this only pins the signature and the visibility.
165    /// let apply: fn(&Decryptor, &mut Object, u32, u16) = Decryptor::decrypt_object;
166    /// let _ = apply;
167    /// ```
168    pub fn decrypt_object(&self, obj: &mut Object, num: u32, gen: u16) {
169        let key = match self.cipher {
170            Cipher::Aesv3 => self.key.clone(), // one file key for every object
171            Cipher::Rc4 | Cipher::Aesv2 => self.object_key(num, gen),
172        };
173        decrypt_in_place(obj, &key, self.cipher, self.encrypt_metadata);
174    }
175
176    /// Per-object key: `MD5(filekey ++ num[0..3] ++ gen[0..2] [++ "sAlT"])`
177    /// truncated to `min(n + 5, 16)` bytes (ISO 32000 §7.6.2, Algorithm 1). The
178    /// `sAlT` suffix is added for AES crypt filters.
179    fn object_key(&self, num: u32, gen: u16) -> Vec<u8> {
180        let mut input = Vec::with_capacity(self.key.len() + 9);
181        input.extend_from_slice(&self.key);
182        input.extend_from_slice(&num.to_le_bytes()[..3]);
183        input.extend_from_slice(&gen.to_le_bytes()[..2]);
184        if self.cipher == Cipher::Aesv2 {
185            input.extend_from_slice(b"sAlT");
186        }
187        let digest = md5(&input);
188        let n = (self.key.len() + 5).min(16);
189        digest[..n].to_vec()
190    }
191}
192
193/// Recursively decrypts every string and stream body reachable from `obj` with
194/// the per-object `key` under `cipher`. When `encrypt_metadata` is false, a
195/// stream whose own dictionary says `/Type /Metadata` was stored in
196/// plaintext (ISO 32000-2 §7.6.4.2, Table 20) and its data is left alone;
197/// the dictionary's own values still walk normally.
198fn decrypt_in_place(obj: &mut Object, key: &[u8], cipher: Cipher, encrypt_metadata: bool) {
199    match obj {
200        Object::String(bytes) => *bytes = decrypt_bytes(cipher, key, bytes),
201        Object::Array(items) => items
202            .iter_mut()
203            .for_each(|it| decrypt_in_place(it, key, cipher, encrypt_metadata)),
204        Object::Dict(dict) => dict
205            .values_mut()
206            .for_each(|v| decrypt_in_place(v, key, cipher, encrypt_metadata)),
207        Object::Stream(stream) => {
208            stream
209                .dict
210                .values_mut()
211                .for_each(|v| decrypt_in_place(v, key, cipher, encrypt_metadata));
212            if !encrypt_metadata && is_metadata_stream(&stream.dict) {
213                return;
214            }
215            stream.data = decrypt_bytes(cipher, key, &stream.data);
216        }
217        _ => {}
218    }
219}
220
221/// Whether `dict` names `/Type /Metadata`.
222fn is_metadata_stream(dict: &Dict) -> bool {
223    dict.get_name("Type").map(|n| n.0.as_str()) == Some("Metadata")
224}
225
226/// Applies `cipher` to one string or stream body with the given `key`.
227fn decrypt_bytes(cipher: Cipher, key: &[u8], data: &[u8]) -> Vec<u8> {
228    match cipher {
229        Cipher::Rc4 => rc4(key, data),
230        Cipher::Aesv2 | Cipher::Aesv3 => aes_cbc_decrypt(key, data),
231    }
232}
233
234/// The Standard stream crypt filter's method (`/CF` → `/StmF` → `/CFM`):
235/// `V2`, `AESV2`, or `Identity`.
236fn crypt_filter_method(enc: &Dict) -> Option<String> {
237    let stmf = enc
238        .get_name("StmF")
239        .map(|n| n.0.as_str())
240        .unwrap_or("StdCF");
241    let filter = enc.get_dict("CF")?.get_dict(stmf)?;
242    Some(filter.get_name("CFM")?.0.clone())
243}
244
245/// Pads or truncates a password to the 32 bytes every legacy algorithm
246/// hashes (ISO 32000 §7.6.3.3, Algorithm 2 step (a)). The empty password
247/// pads to [`PAD`] itself.
248fn pad_password(password: &[u8]) -> [u8; 32] {
249    let mut out = [0u8; 32];
250    let n = password.len().min(32);
251    out[..n].copy_from_slice(&password[..n]);
252    out[n..].copy_from_slice(&PAD[..32 - n]);
253    out
254}
255
256/// The RC4/AESV2 file key `password` actually opens: tried as the user
257/// password (Algorithm 2 + the `/U` check), then as the owner password
258/// (Algorithm 7: the owner key decrypts `/O` back into the padded user
259/// password, which must then verify like any user password).
260fn rc4_family_key(enc: &Dict, id0: &[u8], r: i64, n: usize, password: &[u8]) -> Option<Vec<u8>> {
261    let u = enc.get("U").and_then(Object::as_str_bytes)?;
262    if let Some(key) = md5_file_key(enc, id0, r, n, &pad_password(password)) {
263        if verify_user_password(&key, r, id0, u) {
264            return Some(key);
265        }
266    }
267    // Owner attempt. The owner key comes from the owner password alone
268    // (Algorithm 3 steps (a)-(d)); what it decrypts out of `/O` is the
269    // padded user password, ready for Algorithm 2 verbatim.
270    let o = enc.get("O").and_then(Object::as_str_bytes)?;
271    if o.len() < 32 {
272        return None;
273    }
274    let mut d = md5(&pad_password(password));
275    if r >= 3 {
276        for _ in 0..50 {
277            d = md5(&d[..n]);
278        }
279    }
280    let okey = &d[..n];
281    let recovered = if r == 2 {
282        rc4(okey, &o[..32])
283    } else {
284        let mut x = o[..32].to_vec();
285        for i in (1u8..=19).rev() {
286            let keyed: Vec<u8> = okey.iter().map(|b| b ^ i).collect();
287            x = rc4(&keyed, &x);
288        }
289        rc4(okey, &x)
290    };
291    let padded: [u8; 32] = recovered.get(..32)?.try_into().ok()?;
292    let key = md5_file_key(enc, id0, r, n, &padded)?;
293    verify_user_password(&key, r, id0, u).then_some(key)
294}
295
296/// Algorithm 2: derive the RC4/AESV2 file key from a padded user password.
297fn md5_file_key(enc: &Dict, id0: &[u8], r: i64, n: usize, padded: &[u8; 32]) -> Option<Vec<u8>> {
298    let o = enc.get("O").and_then(Object::as_str_bytes)?;
299    if o.len() < 32 {
300        return None;
301    }
302    let p = enc.get_int("P")?;
303    let mut input = Vec::with_capacity(32 + 32 + 4 + id0.len() + 4);
304    input.extend_from_slice(padded);
305    input.extend_from_slice(&o[..32]);
306    input.extend_from_slice(&(p as i32 as u32).to_le_bytes()); // /P low 32 bits, LE
307    input.extend_from_slice(id0);
308    // Revision 4 with /EncryptMetadata false hashes an extra 0xFFFFFFFF.
309    if r >= 4 && !encrypt_metadata_flag(enc) {
310        input.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
311    }
312    let mut digest = md5(&input);
313    if r >= 3 {
314        for _ in 0..50 {
315            digest = md5(&digest[..n]);
316        }
317    }
318    Some(digest[..n].to_vec())
319}
320
321/// Checks the empty user password by recomputing `/U` and comparing.
322fn verify_user_password(key: &[u8], r: i64, id0: &[u8], u: &[u8]) -> bool {
323    if r == 2 {
324        // Algorithm 4: U = RC4(key, PAD).
325        let computed = rc4(key, &PAD);
326        u.len() >= 32 && computed == u[..32]
327    } else {
328        // Algorithm 5: U = MD5(PAD ++ ID[0]) encrypted with 20 keyed RC4 passes.
329        let mut input = Vec::with_capacity(32 + id0.len());
330        input.extend_from_slice(&PAD);
331        input.extend_from_slice(id0);
332        let mut x = md5(&input).to_vec();
333        x = rc4(key, &x);
334        for i in 1u8..=19 {
335            let keyed: Vec<u8> = key.iter().map(|b| b ^ i).collect();
336            x = rc4(&keyed, &x);
337        }
338        // Only the first 16 bytes are defined; the rest of /U is arbitrary padding.
339        u.len() >= 16 && x[..16] == u[..16]
340    }
341}
342
343/// RC4 stream cipher (symmetric: the same call encrypts and decrypts).
344fn rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
345    debug_assert!(!key.is_empty());
346    let mut s: [u8; 256] = core::array::from_fn(|i| i as u8);
347    let mut j = 0u8;
348    for i in 0..256 {
349        j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
350        s.swap(i, j as usize);
351    }
352    let mut out = Vec::with_capacity(data.len());
353    let (mut i, mut j) = (0u8, 0u8);
354    for &byte in data {
355        i = i.wrapping_add(1);
356        j = j.wrapping_add(s[i as usize]);
357        s.swap(i as usize, j as usize);
358        let k = s[s[i as usize].wrapping_add(s[j as usize]) as usize];
359        out.push(byte ^ k);
360    }
361    out
362}
363
364// --- AES (FIPS-197) and CBC mode -----------------------------------------
365
366/// AES substitution box.
367#[rustfmt::skip]
368const AES_SBOX: [u8; 256] = [
369    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
370    0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
371    0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
372    0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
373    0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
374    0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
375    0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
376    0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
377    0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
378    0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
379    0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
380    0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
381    0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
382    0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
383    0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
384    0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
385];
386
387/// Round constants for key expansion (`RCON[j]` used when `i % Nk == 0`).
388const AES_RCON: [u8; 11] = [
389    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
390];
391
392/// The inverse S-box, derived once from `AES_SBOX`.
393fn aes_inv_sbox() -> [u8; 256] {
394    let mut inv = [0u8; 256];
395    for (i, &s) in AES_SBOX.iter().enumerate() {
396        inv[s as usize] = i as u8;
397    }
398    inv
399}
400
401/// Multiplies two elements of GF(2^8) with the AES reduction polynomial.
402fn gmul(mut a: u8, mut b: u8) -> u8 {
403    let mut p = 0u8;
404    for _ in 0..8 {
405        if b & 1 != 0 {
406            p ^= a;
407        }
408        let hi = a & 0x80;
409        a <<= 1;
410        if hi != 0 {
411            a ^= 0x1b;
412        }
413        b >>= 1;
414    }
415    p
416}
417
418/// Expands a 16- or 32-byte key into `Nr + 1` round keys (state is stored
419/// column-major, so byte `r + 4c` is row `r`, column `c`).
420fn aes_expand_key(key: &[u8]) -> Vec<[u8; 16]> {
421    let nk = key.len() / 4; // 4 (AES-128) or 8 (AES-256)
422    let nr = nk + 6;
423    let total = 4 * (nr + 1);
424    let mut w: Vec<[u8; 4]> = Vec::with_capacity(total);
425    for i in 0..nk {
426        w.push([key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]]);
427    }
428    for i in nk..total {
429        let mut t = w[i - 1];
430        if i.is_multiple_of(nk) {
431            t = [t[1], t[2], t[3], t[0]]; // RotWord
432            for b in &mut t {
433                *b = AES_SBOX[*b as usize]; // SubWord
434            }
435            t[0] ^= AES_RCON[i / nk];
436        } else if nk > 6 && i % nk == 4 {
437            for b in &mut t {
438                *b = AES_SBOX[*b as usize];
439            }
440        }
441        let prev = w[i - nk];
442        w.push([
443            prev[0] ^ t[0],
444            prev[1] ^ t[1],
445            prev[2] ^ t[2],
446            prev[3] ^ t[3],
447        ]);
448    }
449    (0..=nr)
450        .map(|round| {
451            let mut rk = [0u8; 16];
452            for c in 0..4 {
453                rk[4 * c..4 * c + 4].copy_from_slice(&w[4 * round + c]);
454            }
455            rk
456        })
457        .collect()
458}
459
460fn add_round_key(s: &mut [u8; 16], rk: &[u8; 16]) {
461    for (b, k) in s.iter_mut().zip(rk) {
462        *b ^= k;
463    }
464}
465
466fn shift_rows(s: &mut [u8; 16]) {
467    let o = *s;
468    for r in 1..4 {
469        for c in 0..4 {
470            s[r + 4 * c] = o[r + 4 * ((c + r) % 4)];
471        }
472    }
473}
474
475fn inv_shift_rows(s: &mut [u8; 16]) {
476    let o = *s;
477    for r in 1..4 {
478        for c in 0..4 {
479            s[r + 4 * c] = o[r + 4 * ((c + 4 - r) % 4)];
480        }
481    }
482}
483
484fn mix_columns(s: &mut [u8; 16]) {
485    for c in 0..4 {
486        let i = 4 * c;
487        let (a0, a1, a2, a3) = (s[i], s[i + 1], s[i + 2], s[i + 3]);
488        s[i] = gmul(a0, 2) ^ gmul(a1, 3) ^ a2 ^ a3;
489        s[i + 1] = a0 ^ gmul(a1, 2) ^ gmul(a2, 3) ^ a3;
490        s[i + 2] = a0 ^ a1 ^ gmul(a2, 2) ^ gmul(a3, 3);
491        s[i + 3] = gmul(a0, 3) ^ a1 ^ a2 ^ gmul(a3, 2);
492    }
493}
494
495fn inv_mix_columns(s: &mut [u8; 16]) {
496    for c in 0..4 {
497        let i = 4 * c;
498        let (a0, a1, a2, a3) = (s[i], s[i + 1], s[i + 2], s[i + 3]);
499        s[i] = gmul(a0, 14) ^ gmul(a1, 11) ^ gmul(a2, 13) ^ gmul(a3, 9);
500        s[i + 1] = gmul(a0, 9) ^ gmul(a1, 14) ^ gmul(a2, 11) ^ gmul(a3, 13);
501        s[i + 2] = gmul(a0, 13) ^ gmul(a1, 9) ^ gmul(a2, 14) ^ gmul(a3, 11);
502        s[i + 3] = gmul(a0, 11) ^ gmul(a1, 13) ^ gmul(a2, 9) ^ gmul(a3, 14);
503    }
504}
505
506fn aes_encrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
507    #[cfg(target_arch = "aarch64")]
508    if std::arch::is_aarch64_feature_detected!("aes") {
509        // SAFETY: the `aes` target feature was just detected at runtime.
510        unsafe { aes_hw::encrypt_block(s, rks) };
511        return;
512    }
513    #[cfg(target_arch = "x86_64")]
514    if std::arch::is_x86_feature_detected!("aes") {
515        // SAFETY: the `aes` target feature was just detected at runtime.
516        unsafe { aes_hw::encrypt_block(s, rks) };
517        return;
518    }
519    aes_encrypt_block_soft(s, rks);
520}
521
522/// The portable byte-oriented rounds, for CPUs without AES instructions.
523fn aes_encrypt_block_soft(s: &mut [u8; 16], rks: &[[u8; 16]]) {
524    let nr = rks.len() - 1;
525    add_round_key(s, &rks[0]);
526    for rk in &rks[1..nr] {
527        s.iter_mut().for_each(|b| *b = AES_SBOX[*b as usize]);
528        shift_rows(s);
529        mix_columns(s);
530        add_round_key(s, rk);
531    }
532    s.iter_mut().for_each(|b| *b = AES_SBOX[*b as usize]);
533    shift_rows(s);
534    add_round_key(s, &rks[nr]);
535}
536
537fn aes_decrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]], inv_sbox: &[u8; 256]) {
538    #[cfg(target_arch = "aarch64")]
539    if std::arch::is_aarch64_feature_detected!("aes") {
540        // SAFETY: the `aes` target feature was just detected at runtime.
541        unsafe { aes_hw::decrypt_block(s, rks) };
542        return;
543    }
544    #[cfg(target_arch = "x86_64")]
545    if std::arch::is_x86_feature_detected!("aes") {
546        // SAFETY: the `aes` target feature was just detected at runtime.
547        unsafe { aes_hw::decrypt_block(s, rks) };
548        return;
549    }
550    aes_decrypt_block_soft(s, rks, inv_sbox);
551}
552
553/// The portable byte-oriented rounds, for CPUs without AES instructions.
554fn aes_decrypt_block_soft(s: &mut [u8; 16], rks: &[[u8; 16]], inv_sbox: &[u8; 256]) {
555    let nr = rks.len() - 1;
556    add_round_key(s, &rks[nr]);
557    for rk in rks[1..nr].iter().rev() {
558        inv_shift_rows(s);
559        s.iter_mut().for_each(|b| *b = inv_sbox[*b as usize]);
560        add_round_key(s, rk);
561        inv_mix_columns(s);
562    }
563    inv_shift_rows(s);
564    s.iter_mut().for_each(|b| *b = inv_sbox[*b as usize]);
565    add_round_key(s, &rks[0]);
566}
567
568/// AES block rounds on the CPU's AES instructions. The R6 password hash
569/// CBC-encrypts about a megabyte per key derivation (Algorithm 2.B runs
570/// 64+ rounds over a 64-fold repeated block), which the byte-oriented
571/// software rounds above turn into ~500ms per encrypted document; these
572/// paths bring that under a millisecond. Callers detect the `aes` feature
573/// before entering; every function must produce bytes identical to the
574/// software rounds (pinned by the FIPS-197 vectors in the tests below).
575#[cfg(target_arch = "aarch64")]
576mod aes_hw {
577    use core::arch::aarch64::{
578        vaesdq_u8, vaeseq_u8, vaesimcq_u8, vaesmcq_u8, veorq_u8, vld1q_u8, vmovq_n_u8, vst1q_u8,
579    };
580
581    /// # Safety
582    /// Requires the `aes` target feature.
583    #[target_feature(enable = "aes")]
584    pub unsafe fn encrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
585        let nr = rks.len() - 1;
586        // AESE folds AddRoundKey into SubBytes+ShiftRows, so the loop
587        // consumes rks[0..nr-1] and the last round's key lands as a plain
588        // XOR after the final AESE.
589        let mut x = vld1q_u8(s.as_ptr());
590        for rk in &rks[..nr - 1] {
591            x = vaesmcq_u8(vaeseq_u8(x, vld1q_u8(rk.as_ptr())));
592        }
593        x = vaeseq_u8(x, vld1q_u8(rks[nr - 1].as_ptr()));
594        x = veorq_u8(x, vld1q_u8(rks[nr].as_ptr()));
595        vst1q_u8(s.as_mut_ptr(), x);
596    }
597
598    /// # Safety
599    /// Requires the `aes` target feature.
600    #[target_feature(enable = "aes")]
601    pub unsafe fn decrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
602        let nr = rks.len() - 1;
603        // AESD with a zero key is exactly InvSubBytes(InvShiftRows(x)),
604        // which lets the loop mirror the software inverse cipher's
605        // operation order with the round keys untransformed.
606        let zero = vmovq_n_u8(0);
607        let mut x = vld1q_u8(s.as_ptr());
608        x = veorq_u8(x, vld1q_u8(rks[nr].as_ptr()));
609        for rk in rks[1..nr].iter().rev() {
610            x = vaesdq_u8(x, zero);
611            x = veorq_u8(x, vld1q_u8(rk.as_ptr()));
612            x = vaesimcq_u8(x);
613        }
614        x = vaesdq_u8(x, zero);
615        x = veorq_u8(x, vld1q_u8(rks[0].as_ptr()));
616        vst1q_u8(s.as_mut_ptr(), x);
617    }
618}
619
620/// See the aarch64 twin above; same contract, AES-NI instructions.
621#[cfg(target_arch = "x86_64")]
622mod aes_hw {
623    use core::arch::x86_64::{
624        __m128i, _mm_aesdec_si128, _mm_aesdeclast_si128, _mm_aesenc_si128, _mm_aesenclast_si128,
625        _mm_aesimc_si128, _mm_loadu_si128, _mm_storeu_si128, _mm_xor_si128,
626    };
627
628    /// # Safety
629    /// Requires the `aes` target feature.
630    #[target_feature(enable = "aes")]
631    pub unsafe fn encrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
632        let nr = rks.len() - 1;
633        let mut x = _mm_loadu_si128(s.as_ptr().cast::<__m128i>());
634        x = _mm_xor_si128(x, _mm_loadu_si128(rks[0].as_ptr().cast::<__m128i>()));
635        for rk in &rks[1..nr] {
636            x = _mm_aesenc_si128(x, _mm_loadu_si128(rk.as_ptr().cast::<__m128i>()));
637        }
638        x = _mm_aesenclast_si128(x, _mm_loadu_si128(rks[nr].as_ptr().cast::<__m128i>()));
639        _mm_storeu_si128(s.as_mut_ptr().cast::<__m128i>(), x);
640    }
641
642    /// # Safety
643    /// Requires the `aes` target feature.
644    #[target_feature(enable = "aes")]
645    pub unsafe fn decrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
646        let nr = rks.len() - 1;
647        // AESDEC applies InvMixColumns before its key XOR, so the middle
648        // round keys go through AESIMC (the equivalent inverse cipher).
649        let mut x = _mm_loadu_si128(s.as_ptr().cast::<__m128i>());
650        x = _mm_xor_si128(x, _mm_loadu_si128(rks[nr].as_ptr().cast::<__m128i>()));
651        for rk in rks[1..nr].iter().rev() {
652            let dk = _mm_aesimc_si128(_mm_loadu_si128(rk.as_ptr().cast::<__m128i>()));
653            x = _mm_aesdec_si128(x, dk);
654        }
655        x = _mm_aesdeclast_si128(x, _mm_loadu_si128(rks[0].as_ptr().cast::<__m128i>()));
656        _mm_storeu_si128(s.as_mut_ptr().cast::<__m128i>(), x);
657    }
658}
659
660/// AES-CBC decryption of whole blocks (no IV prefix, no padding removal).
661/// Returns an empty vector when the input is not a positive multiple of 16.
662fn aes_cbc_decrypt_blocks(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
663    if data.is_empty() || !data.len().is_multiple_of(16) || iv.len() < 16 {
664        return Vec::new();
665    }
666    let rks = aes_expand_key(key);
667    let inv_sbox = aes_inv_sbox();
668    let mut prev = [0u8; 16];
669    prev.copy_from_slice(&iv[..16]);
670    let mut out = Vec::with_capacity(data.len());
671    for chunk in data.as_chunks::<16>().0 {
672        let mut block = [0u8; 16];
673        block.copy_from_slice(chunk);
674        let cipher = block;
675        aes_decrypt_block(&mut block, &rks, &inv_sbox);
676        for (b, p) in block.iter_mut().zip(&prev) {
677            *b ^= p;
678        }
679        out.extend_from_slice(&block);
680        prev = cipher;
681    }
682    out
683}
684
685/// AES-CBC encryption of whole blocks (no IV prefix, no padding). Used only by
686/// the R6 key-derivation hash.
687fn aes_cbc_encrypt_blocks(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
688    let rks = aes_expand_key(key);
689    let mut prev = [0u8; 16];
690    prev.copy_from_slice(&iv[..16]);
691    let mut out = Vec::with_capacity(data.len());
692    for chunk in data.as_chunks::<16>().0 {
693        let mut block = [0u8; 16];
694        for ((b, c), p) in block.iter_mut().zip(chunk).zip(&prev) {
695            *b = c ^ p;
696        }
697        aes_encrypt_block(&mut block, &rks);
698        out.extend_from_slice(&block);
699        prev = block;
700    }
701    out
702}
703
704/// Decrypts a PDF AES value: the first 16 bytes are the IV, the rest is
705/// CBC-encrypted with PKCS#7 padding. Malformed input yields empty output
706/// rather than garbage.
707fn aes_cbc_decrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
708    if data.len() < 16 {
709        return Vec::new();
710    }
711    let (iv, ct) = data.split_at(16);
712    let mut out = aes_cbc_decrypt_blocks(key, iv, ct);
713    strip_pkcs7(&mut out);
714    out
715}
716
717/// Removes PKCS#7 padding in place if present and well-formed.
718fn strip_pkcs7(data: &mut Vec<u8>) {
719    let Some(&pad) = data.last() else {
720        return;
721    };
722    let pad = pad as usize;
723    if (1..=16).contains(&pad) && pad <= data.len() {
724        let start = data.len() - pad;
725        if data[start..].iter().all(|&b| b as usize == pad) {
726            data.truncate(start);
727        }
728    }
729}
730
731/// Pads `data` to a whole number of 16-byte blocks with PKCS#7: a full
732/// extra block of `0x10` bytes when `data` is already block-aligned, so
733/// [`strip_pkcs7`] always has a marker to remove.
734fn pkcs7_pad(data: &[u8]) -> Vec<u8> {
735    let pad = 16 - data.len() % 16;
736    let mut out = data.to_vec();
737    out.resize(data.len() + pad, pad as u8);
738    out
739}
740
741/// Encrypts a PDF AES value: a fresh 16-byte IV from `rng` followed by the
742/// PKCS#7-padded plaintext under AES-256-CBC, the exact inverse of
743/// [`aes_cbc_decrypt`].
744fn aes_cbc_encrypt(key: &[u8], rng: &mut dyn FnMut(&mut [u8]), data: &[u8]) -> Vec<u8> {
745    let mut iv = [0u8; 16];
746    rng(&mut iv);
747    let mut out = iv.to_vec();
748    out.extend_from_slice(&aes_cbc_encrypt_blocks(key, &iv, &pkcs7_pad(data)));
749    out
750}
751
752// --- SHA-2 (FIPS 180-4): 256, 512 and 384 --------------------------------
753
754#[rustfmt::skip]
755const SHA256_K: [u32; 64] = [
756    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
757    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
758    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
759    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
760    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
761    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
762    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
763    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
764];
765
766/// Streaming SHA-256 (FIPS 180-4): feed bytes with [`Sha256::update`],
767/// close with [`Sha256::finalize`]. Public so the writer can hash a file's
768/// body as it emits it, without holding the whole file in memory; the
769/// one-shot [`sha256`] delegates here.
770#[derive(Debug, Clone)]
771pub struct Sha256 {
772    h: [u32; 8],
773    tail: [u8; 64],
774    tail_len: usize,
775    total: u64,
776}
777
778impl Default for Sha256 {
779    fn default() -> Sha256 {
780        Sha256::new()
781    }
782}
783
784impl Sha256 {
785    /// A hasher in the FIPS 180-4 initial state.
786    pub fn new() -> Sha256 {
787        Sha256 {
788            h: [
789                0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
790                0x5be0cd19,
791            ],
792            tail: [0u8; 64],
793            tail_len: 0,
794            total: 0,
795        }
796    }
797
798    /// Absorbs `input`. Slice boundaries do not affect the digest: any
799    /// sequence of updates whose concatenation is the same message yields
800    /// the same [`Sha256::finalize`] result. Whole blocks are compressed
801    /// straight from `input`; only a partial trailing block is buffered.
802    pub fn update(&mut self, input: &[u8]) {
803        self.total = self.total.wrapping_add(input.len() as u64);
804        let mut input = input;
805        if self.tail_len > 0 {
806            let take = input.len().min(64 - self.tail_len);
807            self.tail[self.tail_len..self.tail_len + take].copy_from_slice(&input[..take]);
808            self.tail_len += take;
809            input = &input[take..];
810            if self.tail_len < 64 {
811                return;
812            }
813            let block = self.tail;
814            sha256_compress(&mut self.h, &block);
815            self.tail_len = 0;
816        }
817        let (blocks, rest) = input.as_chunks::<64>();
818        for block in blocks {
819            sha256_compress(&mut self.h, block);
820        }
821        self.tail[..rest.len()].copy_from_slice(rest);
822        self.tail_len = rest.len();
823    }
824
825    /// Pads and returns the digest of everything absorbed so far.
826    pub fn finalize(mut self) -> [u8; 32] {
827        // Final padding touches at most two blocks: 0x80, zeros, and the
828        // bit length in the last 8 bytes (FIPS 180-4).
829        let mut pad = [0u8; 128];
830        pad[..self.tail_len].copy_from_slice(&self.tail[..self.tail_len]);
831        pad[self.tail_len] = 0x80;
832        let padded = if self.tail_len < 56 { 64 } else { 128 };
833        let bitlen = self.total.wrapping_mul(8);
834        pad[padded - 8..padded].copy_from_slice(&bitlen.to_be_bytes());
835        for block in pad[..padded].as_chunks::<64>().0 {
836            sha256_compress(&mut self.h, block);
837        }
838        let mut out = [0u8; 32];
839        for (i, word) in self.h.iter().enumerate() {
840            out[4 * i..4 * i + 4].copy_from_slice(&word.to_be_bytes());
841        }
842        out
843    }
844}
845
846/// SHA-256 of `input` (FIPS 180-4); public so the writer can derive its `/ID`.
847pub fn sha256(input: &[u8]) -> [u8; 32] {
848    let mut hasher = Sha256::new();
849    hasher.update(input);
850    hasher.finalize()
851}
852
853fn sha256_compress(h: &mut [u32; 8], block: &[u8; 64]) {
854    #[cfg(target_arch = "aarch64")]
855    if std::arch::is_aarch64_feature_detected!("sha2") {
856        // SAFETY: the `sha2` target feature was just detected at runtime.
857        unsafe { sha_hw::compress256(h, block) };
858        return;
859    }
860    sha256_compress_soft(h, block);
861}
862
863/// The portable compression, for CPUs without SHA-256 instructions.
864fn sha256_compress_soft(h: &mut [u32; 8], block: &[u8; 64]) {
865    let mut w = [0u32; 64];
866    for (word, bytes) in w.iter_mut().zip(block.as_chunks::<4>().0) {
867        *word = u32::from_be_bytes(*bytes);
868    }
869    for i in 16..64 {
870        let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
871        let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
872        w[i] = w[i - 16]
873            .wrapping_add(s0)
874            .wrapping_add(w[i - 7])
875            .wrapping_add(s1);
876    }
877    let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = *h;
878    for (k, wi) in SHA256_K.iter().zip(&w) {
879        let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
880        let ch = (e & f) ^ ((!e) & g);
881        let t1 = hh
882            .wrapping_add(s1)
883            .wrapping_add(ch)
884            .wrapping_add(*k)
885            .wrapping_add(*wi);
886        let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
887        let maj = (a & b) ^ (a & c) ^ (b & c);
888        let t2 = s0.wrapping_add(maj);
889        hh = g;
890        g = f;
891        f = e;
892        e = d.wrapping_add(t1);
893        d = c;
894        c = b;
895        b = a;
896        a = t1.wrapping_add(t2);
897    }
898    for (hv, v) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
899        *hv = hv.wrapping_add(v);
900    }
901}
902
903#[rustfmt::skip]
904const SHA512_K: [u64; 80] = [
905    0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
906    0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
907    0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
908    0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
909    0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
910    0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
911    0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
912    0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
913    0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
914    0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
915    0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
916    0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
917    0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
918    0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
919    0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
920    0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
921    0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
922    0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
923    0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
924    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817,
925];
926
927fn sha512_core(input: &[u8], mut h: [u64; 8]) -> [u64; 8] {
928    let (blocks, tail) = input.as_chunks::<128>();
929    for block in blocks {
930        sha512_compress(&mut h, block);
931    }
932    // Final padding touches at most two blocks: 0x80, zeros, and the bit
933    // length in the last 16 bytes (FIPS 180-4) — the message itself is
934    // hashed in place above, never copied.
935    let mut pad = [0u8; 256];
936    pad[..tail.len()].copy_from_slice(tail);
937    pad[tail.len()] = 0x80;
938    let padded = if tail.len() < 112 { 128 } else { 256 };
939    let bitlen = (input.len() as u128).wrapping_mul(8);
940    pad[padded - 16..padded].copy_from_slice(&bitlen.to_be_bytes());
941    for block in pad[..padded].as_chunks::<128>().0 {
942        sha512_compress(&mut h, block);
943    }
944    h
945}
946
947fn sha512_compress(h: &mut [u64; 8], block: &[u8; 128]) {
948    #[cfg(target_arch = "aarch64")]
949    if std::arch::is_aarch64_feature_detected!("sha3") {
950        // SAFETY: the `sha3` target feature (SHA-512 instructions) was
951        // just detected at runtime.
952        unsafe { sha_hw::compress512(h, block) };
953        return;
954    }
955    sha512_compress_soft(h, block);
956}
957
958/// The portable compression, for CPUs without SHA-512 instructions.
959fn sha512_compress_soft(h: &mut [u64; 8], block: &[u8; 128]) {
960    let mut w = [0u64; 80];
961    for (word, bytes) in w.iter_mut().zip(block.as_chunks::<8>().0) {
962        *word = u64::from_be_bytes(*bytes);
963    }
964    for i in 16..80 {
965        let s0 = w[i - 15].rotate_right(1) ^ w[i - 15].rotate_right(8) ^ (w[i - 15] >> 7);
966        let s1 = w[i - 2].rotate_right(19) ^ w[i - 2].rotate_right(61) ^ (w[i - 2] >> 6);
967        w[i] = w[i - 16]
968            .wrapping_add(s0)
969            .wrapping_add(w[i - 7])
970            .wrapping_add(s1);
971    }
972    let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = *h;
973    for (k, wi) in SHA512_K.iter().zip(&w) {
974        let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
975        let ch = (e & f) ^ ((!e) & g);
976        let t1 = hh
977            .wrapping_add(s1)
978            .wrapping_add(ch)
979            .wrapping_add(*k)
980            .wrapping_add(*wi);
981        let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
982        let maj = (a & b) ^ (a & c) ^ (b & c);
983        let t2 = s0.wrapping_add(maj);
984        hh = g;
985        g = f;
986        f = e;
987        e = d.wrapping_add(t1);
988        d = c;
989        c = b;
990        b = a;
991        a = t1.wrapping_add(t2);
992    }
993    for (hv, v) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
994        *hv = hv.wrapping_add(v);
995    }
996}
997
998/// SHA-2 compressions on the CPU's SHA instructions. Algorithm 2.B hashes
999/// roughly a megabyte per key derivation across its rounds — after the AES
1000/// rounds moved to hardware this hashing was the remaining ~1-3ms of every
1001/// encrypted-document open (PMU counters put it 60x below the memory-bound
1002/// probe and at the ALU probe's branch signature: a pure serial dependency
1003/// chain, which is exactly what the SHA instructions collapse). Callers
1004/// detect `sha2`/`sha3` before entering; results are pinned to the FIPS
1005/// 180 vectors in the tests below.
1006#[cfg(target_arch = "aarch64")]
1007mod sha_hw {
1008    use core::arch::aarch64::{
1009        uint32x4_t, uint64x2_t, vaddq_u32, vaddq_u64, vextq_u64, vld1q_u32, vld1q_u64, vld1q_u8,
1010        vreinterpretq_u32_u8, vreinterpretq_u64_u8, vrev32q_u8, vrev64q_u8, vsha256h2q_u32,
1011        vsha256hq_u32, vsha256su0q_u32, vsha256su1q_u32, vsha512h2q_u64, vsha512hq_u64,
1012        vsha512su0q_u64, vsha512su1q_u64, vst1q_u32,
1013    };
1014
1015    use super::{SHA256_K, SHA512_K};
1016
1017    /// # Safety
1018    /// Requires the `sha2` target feature.
1019    #[target_feature(enable = "sha2")]
1020    pub unsafe fn compress256(h: &mut [u32; 8], block: &[u8; 64]) {
1021        let mut abcd = vld1q_u32(h.as_ptr());
1022        let mut efgh = vld1q_u32(h.as_ptr().add(4));
1023        let saved = (abcd, efgh);
1024        let mut w: [uint32x4_t; 4] = [
1025            vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(block.as_ptr()))),
1026            vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(block.as_ptr().add(16)))),
1027            vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(block.as_ptr().add(32)))),
1028            vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(block.as_ptr().add(48)))),
1029        ];
1030        for step in 0..16 {
1031            let wk = vaddq_u32(w[step % 4], vld1q_u32(SHA256_K.as_ptr().add(4 * step)));
1032            // The last three windows feed no further schedule.
1033            if step < 12 {
1034                w[step % 4] = vsha256su1q_u32(
1035                    vsha256su0q_u32(w[step % 4], w[(step + 1) % 4]),
1036                    w[(step + 2) % 4],
1037                    w[(step + 3) % 4],
1038                );
1039            }
1040            let prev = abcd;
1041            abcd = vsha256hq_u32(abcd, efgh, wk);
1042            efgh = vsha256h2q_u32(efgh, prev, wk);
1043        }
1044        vst1q_u32(h.as_mut_ptr(), vaddq_u32(abcd, saved.0));
1045        vst1q_u32(h.as_mut_ptr().add(4), vaddq_u32(efgh, saved.1));
1046    }
1047
1048    /// # Safety
1049    /// Requires the `sha3` target feature (the SHA-512 instructions).
1050    #[target_feature(enable = "sha2,sha3")]
1051    pub unsafe fn compress512(h: &mut [u64; 8], block: &[u8; 128]) {
1052        // State as register pairs s = [ab, cd, ef, gh]. Each step covers
1053        // two rounds; the register playing the "gh" role rotates
1054        // gh -> ef -> cd -> ab, which the r index tracks.
1055        let mut s: [uint64x2_t; 4] = [
1056            vld1q_u64(h.as_ptr()),
1057            vld1q_u64(h.as_ptr().add(2)),
1058            vld1q_u64(h.as_ptr().add(4)),
1059            vld1q_u64(h.as_ptr().add(6)),
1060        ];
1061        let saved = s;
1062        let mut w: [uint64x2_t; 8] = [core::mem::zeroed(); 8];
1063        for (pair, bytes) in w.iter_mut().zip(block.as_chunks::<16>().0) {
1064            *pair = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(bytes.as_ptr())));
1065        }
1066        for step in 0..40 {
1067            let r = 3 - (step % 4);
1068            let wk = vaddq_u64(w[step % 8], vld1q_u64(SHA512_K.as_ptr().add(2 * step)));
1069            let sum = vaddq_u64(vextq_u64::<1>(wk, wk), s[r]);
1070            let im = vsha512hq_u64(
1071                sum,
1072                vextq_u64::<1>(s[(r + 3) % 4], s[r]),
1073                vextq_u64::<1>(s[(r + 2) % 4], s[(r + 3) % 4]),
1074            );
1075            let updated = vsha512h2q_u64(im, s[(r + 2) % 4], s[(r + 1) % 4]);
1076            s[(r + 2) % 4] = vaddq_u64(s[(r + 2) % 4], im);
1077            s[r] = updated;
1078            // The last four windows feed no further schedule.
1079            if step < 32 {
1080                let i = step % 8;
1081                w[i] = vsha512su1q_u64(
1082                    vsha512su0q_u64(w[i], w[(i + 1) % 8]),
1083                    w[(i + 7) % 8],
1084                    vextq_u64::<1>(w[(i + 4) % 8], w[(i + 5) % 8]),
1085                );
1086            }
1087        }
1088        for (i, (out, kept)) in s.iter().zip(saved).enumerate() {
1089            let summed = vaddq_u64(*out, kept);
1090            core::arch::aarch64::vst1q_u64(h.as_mut_ptr().add(2 * i), summed);
1091        }
1092    }
1093}
1094
1095fn sha512(input: &[u8]) -> Vec<u8> {
1096    let h = sha512_core(
1097        input,
1098        [
1099            0x6a09e667f3bcc908,
1100            0xbb67ae8584caa73b,
1101            0x3c6ef372fe94f82b,
1102            0xa54ff53a5f1d36f1,
1103            0x510e527fade682d1,
1104            0x9b05688c2b3e6c1f,
1105            0x1f83d9abfb41bd6b,
1106            0x5be0cd19137e2179,
1107        ],
1108    );
1109    h.iter().flat_map(|w| w.to_be_bytes()).collect()
1110}
1111
1112fn sha384(input: &[u8]) -> Vec<u8> {
1113    let h = sha512_core(
1114        input,
1115        [
1116            0xcbbb9d5dc1059ed8,
1117            0x629a292a367cd507,
1118            0x9159015a3070dd17,
1119            0x152fecd8f70e5939,
1120            0x67332667ffc00b31,
1121            0x8eb44a8768581511,
1122            0xdb0c2e0d64f98fa7,
1123            0x47b5481dbefa4fa4,
1124        ],
1125    );
1126    h.iter().take(6).flat_map(|w| w.to_be_bytes()).collect()
1127}
1128
1129/// Recovers the AES-256 file key `password` opens (ISO 32000-2 §7.6.4.3.3,
1130/// Algorithm 2.A) for revisions 5 and 6: as the user password against
1131/// `/U`+`/UE`, then as the owner password against `/O`+`/OE` (whose hashes
1132/// additionally salt in the first 48 bytes of `/U`). Passwords longer than
1133/// the 127 UTF-8 bytes the algorithm defines are truncated.
1134fn aesv3_key(enc: &Dict, r: i64, password: &[u8]) -> Option<Vec<u8>> {
1135    let pw = &password[..password.len().min(127)];
1136    let u = enc.get("U").and_then(Object::as_str_bytes)?;
1137    if u.len() < 48 {
1138        return None;
1139    }
1140    // Fast paths: derive each candidate file key straight from its key
1141    // salt and let the encrypted /Perms confirm it (ISO 32000-2 Algorithm
1142    // 2.A step g validates exactly this way) — one Algorithm 2.B hash per
1143    // path instead of two. Algorithm 2.B is the whole cost of opening an
1144    // encrypted document, so this halves it for every file whose /Perms
1145    // is intact; a wrong password decrypts /Perms to garbage and falls
1146    // through to the full U/O validation below, unchanged.
1147    let perms = enc.get("Perms").and_then(Object::as_str_bytes);
1148    let o = enc.get("O").and_then(Object::as_str_bytes);
1149    if let Some(perms) = perms.filter(|p| p.len() >= 16) {
1150        if let Some(ue) = enc.get("UE").and_then(Object::as_str_bytes) {
1151            if ue.len() >= 32 {
1152                let intermediate = hash_2b(r, pw, &u[40..48], &[]);
1153                let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &ue[..32]);
1154                if file_key.len() == 32 && perms_marker_valid(&file_key, perms) {
1155                    return Some(file_key);
1156                }
1157            }
1158        }
1159        if let (Some(o), Some(oe)) = (o, enc.get("OE").and_then(Object::as_str_bytes)) {
1160            if o.len() >= 48 && oe.len() >= 32 {
1161                let intermediate = hash_2b(r, pw, &o[40..48], &u[..48]);
1162                let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &oe[..32]);
1163                if file_key.len() == 32 && perms_marker_valid(&file_key, perms) {
1164                    return Some(file_key);
1165                }
1166            }
1167        }
1168    }
1169    if let Some(ue) = enc.get("UE").and_then(Object::as_str_bytes) {
1170        if ue.len() >= 32 && hash_2b(r, pw, &u[32..40], &[])[..32] == u[..32] {
1171            let intermediate = hash_2b(r, pw, &u[40..48], &[]);
1172            let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &ue[..32]);
1173            if file_key.len() == 32 {
1174                return Some(file_key);
1175            }
1176        }
1177    }
1178    let o = o?;
1179    let oe = enc.get("OE").and_then(Object::as_str_bytes)?;
1180    if o.len() < 48 || oe.len() < 32 {
1181        return None;
1182    }
1183    if hash_2b(r, pw, &o[32..40], &u[..48])[..32] != o[..32] {
1184        return None;
1185    }
1186    let intermediate = hash_2b(r, pw, &o[40..48], &u[..48]);
1187    let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &oe[..32]);
1188    (file_key.len() == 32).then_some(file_key)
1189}
1190
1191/// Whether `file_key` decrypts `/Perms` to its mandated marker: bytes 9-11
1192/// spell "adb" (ISO 32000-2 Algorithm 2.A step g; AES-256 ECB, no IV).
1193fn perms_marker_valid(file_key: &[u8], perms: &[u8]) -> bool {
1194    let rks = aes_expand_key(file_key);
1195    let inv_sbox = aes_inv_sbox();
1196    let mut block = [0u8; 16];
1197    block.copy_from_slice(&perms[..16]);
1198    aes_decrypt_block(&mut block, &rks, &inv_sbox);
1199    &block[9..12] == b"adb"
1200}
1201
1202/// The revision-6 password hash (ISO 32000-2, Algorithm 2.B); a plain SHA-256
1203/// for revision 5.
1204fn hash_2b(r: i64, password: &[u8], salt: &[u8], udata: &[u8]) -> Vec<u8> {
1205    let mut seed = Vec::with_capacity(password.len() + salt.len() + udata.len());
1206    seed.extend_from_slice(password);
1207    seed.extend_from_slice(salt);
1208    seed.extend_from_slice(udata);
1209    let mut k = sha256(&seed).to_vec();
1210    if r < 6 {
1211        return k; // revision 5: a single SHA-256
1212    }
1213    let mut round = 0usize;
1214    let mut k1 = Vec::with_capacity(64 * (password.len() + 64 + udata.len()));
1215    loop {
1216        k1.clear();
1217        for _ in 0..64 {
1218            k1.extend_from_slice(password);
1219            k1.extend_from_slice(&k);
1220            k1.extend_from_slice(udata);
1221        }
1222        let e = aes_cbc_encrypt_blocks(&k[..16], &k[16..32], &k1);
1223        let modulus = e[..16].iter().map(|&b| u32::from(b)).sum::<u32>() % 3;
1224        k = match modulus {
1225            0 => sha256(&e).to_vec(),
1226            1 => sha384(&e),
1227            _ => sha512(&e),
1228        };
1229        round += 1;
1230        if round >= 64 && usize::from(*e.last().unwrap()) <= round - 32 {
1231            break;
1232        }
1233    }
1234    k.truncate(32);
1235    k
1236}
1237
1238/// Freshly generated key material for a revision-6 `/Encrypt` dictionary:
1239/// the random file key plus `/U`, `/UE`, `/O`, `/OE` and `/Perms` derived
1240/// from it (ISO 32000-2 §7.6.4.3.4-3.6, Algorithms 8, 9 and 10).
1241pub(crate) struct KeyMaterial {
1242    file_key: [u8; 32],
1243    u: Vec<u8>,
1244    ue: Vec<u8>,
1245    o: Vec<u8>,
1246    oe: Vec<u8>,
1247    perms: Vec<u8>,
1248}
1249
1250/// Builds a [`KeyMaterial`] for `user_pw`/`owner_pw` at permission bits `p`
1251/// (ISO 32000-2 §7.6.4.2, Table 22), recording whether metadata is left
1252/// unencrypted. `rng` fills the random bytes Algorithms 8-10 call for: the
1253/// 32-byte file key, the user and owner validation/key salts, and the
1254/// 4-byte `/Perms` pad. Passwords longer than the 127 UTF-8 bytes the
1255/// algorithm defines are truncated, matching the validation side.
1256pub(crate) fn r6_key_material(
1257    user_pw: &[u8],
1258    owner_pw: &[u8],
1259    p: i32,
1260    encrypt_metadata: bool,
1261    rng: &mut dyn FnMut(&mut [u8]),
1262) -> KeyMaterial {
1263    let user_pw = &user_pw[..user_pw.len().min(127)];
1264    let owner_pw = &owner_pw[..owner_pw.len().min(127)];
1265
1266    let mut file_key = [0u8; 32];
1267    rng(&mut file_key);
1268
1269    // Algorithm 8: /U and /UE from a 16-byte random pad, the first 8
1270    // bytes the validation salt and the last 8 the key salt.
1271    let mut user_salt = [0u8; 16];
1272    rng(&mut user_salt);
1273    let mut u = hash_2b(6, user_pw, &user_salt[..8], &[]);
1274    u.extend_from_slice(&user_salt);
1275    let user_intermediate = hash_2b(6, user_pw, &user_salt[8..], &[]);
1276    let ue = aes_cbc_encrypt_blocks(&user_intermediate, &[0u8; 16], &file_key);
1277
1278    // Algorithm 9: /O and /OE, the same derivation but hashing in the
1279    // first 48 bytes of /U as well.
1280    let mut owner_salt = [0u8; 16];
1281    rng(&mut owner_salt);
1282    let mut o = hash_2b(6, owner_pw, &owner_salt[..8], &u[..48]);
1283    o.extend_from_slice(&owner_salt);
1284    let owner_intermediate = hash_2b(6, owner_pw, &owner_salt[8..], &u[..48]);
1285    let oe = aes_cbc_encrypt_blocks(&owner_intermediate, &[0u8; 16], &file_key);
1286
1287    // Algorithm 10: /Perms, the file key applied to a single AES-256 ECB
1288    // block (not CBC).
1289    let mut perms_block = [0u8; 16];
1290    perms_block[..4].copy_from_slice(&p.to_le_bytes());
1291    perms_block[4..8].copy_from_slice(&[0xff; 4]);
1292    perms_block[8] = if encrypt_metadata { b'T' } else { b'F' };
1293    perms_block[9..12].copy_from_slice(b"adb");
1294    rng(&mut perms_block[12..]);
1295    aes_encrypt_block(&mut perms_block, &aes_expand_key(&file_key));
1296
1297    KeyMaterial {
1298        file_key,
1299        u,
1300        ue,
1301        o,
1302        oe,
1303        perms: perms_block.to_vec(),
1304    }
1305}
1306
1307/// Fills `buf` with operating-system random bytes. Not available on
1308/// `wasm32-unknown-unknown`, where `getrandom` is not a dependency at all;
1309/// use [`Encryptor::aes256_with_rng`] there with a caller-supplied source.
1310#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1311pub(crate) fn fill_os_random(buf: &mut [u8]) {
1312    getrandom::fill(buf).expect("OS random number generator unavailable");
1313}
1314
1315/// The eight standard-handler permission bits a document opened under the
1316/// user password (rather than the owner password) is granted (ISO 32000-2
1317/// §7.6.4.2, Table 22). Bits the standard reserves are not represented
1318/// here; `p_value` fixes them to the values it mandates.
1319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1320pub struct Permissions {
1321    /// Print the document, at full quality only when `print_hires` is also
1322    /// set (Table 22 bit 3).
1323    pub print: bool,
1324    /// Modify the document's contents, beyond what `annotate`,
1325    /// `fill_forms` and `assemble` separately cover (Table 22 bit 4).
1326    pub modify: bool,
1327    /// Copy or otherwise extract text and graphics (Table 22 bit 5).
1328    pub copy: bool,
1329    /// Add or modify text annotations and fill form fields (Table 22 bit
1330    /// 6).
1331    pub annotate: bool,
1332    /// Fill in existing form fields even without `annotate` (Table 22 bit
1333    /// 9).
1334    pub fill_forms: bool,
1335    /// Extract text and graphics for accessibility (Table 22 bit 10).
1336    pub accessibility: bool,
1337    /// Assemble the document: insert, rotate or delete pages, create
1338    /// bookmarks or thumbnails (Table 22 bit 11).
1339    pub assemble: bool,
1340    /// Print at full quality; without it, printing (if `print` allows it)
1341    /// may be limited to a low-resolution rasterization (Table 22 bit 12).
1342    pub print_hires: bool,
1343}
1344
1345/// The permission names [`Permissions::from_names`] accepts, in the order
1346/// named in `--allow`/`allow` help text and error messages: `print`,
1347/// `modify`, `copy`, `annotate`, `fill-forms`, `accessibility`, `assemble`,
1348/// `print-hires`.
1349pub const PERMISSION_NAMES: [&str; 8] = [
1350    "print",
1351    "modify",
1352    "copy",
1353    "annotate",
1354    "fill-forms",
1355    "accessibility",
1356    "assemble",
1357    "print-hires",
1358];
1359
1360impl Permissions {
1361    /// Every permission granted.
1362    pub fn all() -> Permissions {
1363        Permissions {
1364            print: true,
1365            modify: true,
1366            copy: true,
1367            annotate: true,
1368            fill_forms: true,
1369            accessibility: true,
1370            assemble: true,
1371            print_hires: true,
1372        }
1373    }
1374
1375    /// Every permission named in `names` granted, everything else denied.
1376    /// The first name not in [`PERMISSION_NAMES`] comes back as `Err`,
1377    /// carrying that name alone: the CLI and the Python bindings each wrap
1378    /// it in their own error type and message text.
1379    pub fn from_names<'a>(names: impl IntoIterator<Item = &'a str>) -> Result<Permissions, String> {
1380        let mut permissions = Permissions {
1381            print: false,
1382            modify: false,
1383            copy: false,
1384            annotate: false,
1385            fill_forms: false,
1386            accessibility: false,
1387            assemble: false,
1388            print_hires: false,
1389        };
1390        for name in names {
1391            match name {
1392                "print" => permissions.print = true,
1393                "modify" => permissions.modify = true,
1394                "copy" => permissions.copy = true,
1395                "annotate" => permissions.annotate = true,
1396                "fill-forms" => permissions.fill_forms = true,
1397                "accessibility" => permissions.accessibility = true,
1398                "assemble" => permissions.assemble = true,
1399                "print-hires" => permissions.print_hires = true,
1400                other => return Err(other.to_string()),
1401            }
1402        }
1403        Ok(permissions)
1404    }
1405
1406    /// The `/P` integer this set of permissions encodes (ISO 32000-2
1407    /// §7.6.4.2, Table 22): bits 1 and 2 are reserved and always cleared;
1408    /// bits 3, 4, 5, 6, 9, 10, 11 and 12 carry `print`, `modify`, `copy`,
1409    /// `annotate`, `fill_forms`, `accessibility`, `assemble` and
1410    /// `print_hires` in that order; every other bit is reserved and always
1411    /// set. Granting everything therefore yields `-4`.
1412    pub(crate) fn p_value(&self) -> i32 {
1413        let flags: [(u32, bool); 8] = [
1414            (3, self.print),
1415            (4, self.modify),
1416            (5, self.copy),
1417            (6, self.annotate),
1418            (9, self.fill_forms),
1419            (10, self.accessibility),
1420            (11, self.assemble),
1421            (12, self.print_hires),
1422        ];
1423        let mut bits: u32 = !0b11; // every bit set, then clear the two reserved low bits
1424        for (bit, granted) in flags {
1425            if !granted {
1426                bits &= !(1u32 << (bit - 1));
1427            }
1428        }
1429        bits as i32
1430    }
1431}
1432
1433impl Default for Permissions {
1434    /// Every permission granted, same as [`Permissions::all`].
1435    fn default() -> Permissions {
1436        Permissions::all()
1437    }
1438}
1439
1440/// Builds the complete AES-256, revision 6 `/Encrypt` dictionary
1441/// (ISO 32000-2 §7.6.4.2) from generated key material: `/Filter /Standard`,
1442/// `/V 5`, `/R 6`, `/Length 256`, a `/CF` naming the standard crypt filter
1443/// as `/AESV3` with a 32-byte key, `/StmF` and `/StrF` both pointing at it,
1444/// and the `/U`, `/UE`, `/O`, `/OE`, `/P` and `/Perms` entries.
1445fn aes256_encrypt_dict(material: &KeyMaterial, p: i32) -> Dict {
1446    let mut std_cf = Dict::new();
1447    std_cf.insert(
1448        Name("CFM".to_string()),
1449        Object::Name(Name("AESV3".to_string())),
1450    );
1451    std_cf.insert(Name("Length".to_string()), Object::Int(32));
1452    let mut cf = Dict::new();
1453    cf.insert(Name("StdCF".to_string()), Object::Dict(std_cf));
1454
1455    let mut dict = Dict::new();
1456    dict.insert(
1457        Name("Filter".to_string()),
1458        Object::Name(Name("Standard".to_string())),
1459    );
1460    dict.insert(Name("V".to_string()), Object::Int(5));
1461    dict.insert(Name("R".to_string()), Object::Int(6));
1462    dict.insert(Name("Length".to_string()), Object::Int(256));
1463    dict.insert(Name("CF".to_string()), Object::Dict(cf));
1464    dict.insert(
1465        Name("StmF".to_string()),
1466        Object::Name(Name("StdCF".to_string())),
1467    );
1468    dict.insert(
1469        Name("StrF".to_string()),
1470        Object::Name(Name("StdCF".to_string())),
1471    );
1472    dict.insert(Name("P".to_string()), Object::Int(i64::from(p)));
1473    dict.insert(Name("U".to_string()), Object::String(material.u.clone()));
1474    dict.insert(Name("UE".to_string()), Object::String(material.ue.clone()));
1475    dict.insert(Name("O".to_string()), Object::String(material.o.clone()));
1476    dict.insert(Name("OE".to_string()), Object::String(material.oe.clone()));
1477    dict.insert(
1478        Name("Perms".to_string()),
1479        Object::String(material.perms.clone()),
1480    );
1481    dict
1482}
1483
1484/// A configured Standard-handler encryptor: AES-256, revision 6, the write
1485/// side of [`Decryptor`]. Built by [`Encryptor::aes256`] or
1486/// [`Encryptor::aes256_with_rng`], which also return the complete
1487/// `/Encrypt` dictionary for the trailer.
1488#[allow(
1489    clippy::type_complexity,
1490    reason = "Box<dyn FnMut(&mut [u8]) + Send> names the one random-byte source the write path takes; a type alias would only rename it"
1491)]
1492pub struct Encryptor {
1493    file_key: [u8; 32],
1494    rng: Box<dyn FnMut(&mut [u8]) + Send>,
1495}
1496
1497impl Encryptor {
1498    /// AES-256 (`/V` 5, `/R` 6) key material from the operating system's
1499    /// random source (ISO 32000-2 §7.6.4.3). Returns the encryptor plus the
1500    /// complete `/Encrypt` dictionary to place in the trailer. Passwords
1501    /// encode as UTF-8 and are truncated to 127 bytes, matching the reader.
1502    ///
1503    /// Not available on `wasm32-unknown-unknown`: there is no operating
1504    /// system random source to draw from there. Use
1505    /// [`Encryptor::aes256_with_rng`] instead, with caller-supplied
1506    /// randomness (for example from the host's `crypto.getRandomValues`).
1507    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1508    pub fn aes256(
1509        user_password: &str,
1510        owner_password: &str,
1511        permissions: Permissions,
1512    ) -> (Encryptor, Dict) {
1513        Encryptor::aes256_with_rng(
1514            user_password,
1515            owner_password,
1516            permissions,
1517            Box::new(fill_os_random),
1518        )
1519    }
1520
1521    /// [`Encryptor::aes256`] with a caller-supplied source of random bytes
1522    /// in place of the operating system's, for reproducible tests.
1523    #[allow(clippy::type_complexity, reason = "see Encryptor's rng field")]
1524    pub fn aes256_with_rng(
1525        user_password: &str,
1526        owner_password: &str,
1527        permissions: Permissions,
1528        rng: Box<dyn FnMut(&mut [u8]) + Send>,
1529    ) -> (Encryptor, Dict) {
1530        let mut rng = rng;
1531        let p = permissions.p_value();
1532        let material = r6_key_material(
1533            user_password.as_bytes(),
1534            owner_password.as_bytes(),
1535            p,
1536            true,
1537            &mut *rng,
1538        );
1539        let dict = aes256_encrypt_dict(&material, p);
1540        let encryptor = Encryptor {
1541            file_key: material.file_key,
1542            rng,
1543        };
1544        (encryptor, dict)
1545    }
1546
1547    /// Encrypts one indirect object's strings and stream data in place, the
1548    /// exact inverse of [`Decryptor::decrypt_object`]: every string and
1549    /// stream body gets a fresh random IV and PKCS#7 padding before
1550    /// AES-256-CBC under the file key. AESV3 applies the same key to every
1551    /// object, so `num` and `gen` are accepted and ignored here, purely for
1552    /// signature symmetry with `decrypt_object`; a future per-object write
1553    /// cipher (an AESV2 write path, say) would need to start deriving its
1554    /// key from them instead of discarding them.
1555    pub fn encrypt_object(&mut self, obj: &mut Object, num: u32, gen: u16) {
1556        let _ = (num, gen);
1557        encrypt_in_place(obj, &self.file_key, &mut *self.rng);
1558    }
1559}
1560
1561/// Recursively encrypts every string and stream body reachable from `obj`
1562/// with the file `key`, mirroring [`decrypt_in_place`]'s walk exactly:
1563/// `String`, `Array`, `Dict` values and `Stream` (dict values plus data);
1564/// every other object type is left untouched.
1565fn encrypt_in_place(obj: &mut Object, key: &[u8], rng: &mut dyn FnMut(&mut [u8])) {
1566    match obj {
1567        Object::String(bytes) => *bytes = aes_cbc_encrypt(key, rng, bytes),
1568        Object::Array(items) => {
1569            for it in items {
1570                encrypt_in_place(it, key, &mut *rng);
1571            }
1572        }
1573        Object::Dict(dict) => {
1574            for v in dict.values_mut() {
1575                encrypt_in_place(v, key, &mut *rng);
1576            }
1577        }
1578        Object::Stream(stream) => {
1579            for v in stream.dict.values_mut() {
1580                encrypt_in_place(v, key, &mut *rng);
1581            }
1582            stream.data = aes_cbc_encrypt(key, rng, &stream.data);
1583        }
1584        _ => {}
1585    }
1586}
1587
1588/// Per-round left-rotation amounts (RFC 1321).
1589#[rustfmt::skip]
1590const MD5_S: [u32; 64] = [
1591    7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
1592    5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
1593    4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
1594    6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
1595];
1596
1597/// Per-round additive constants `floor(2^32 * abs(sin(i + 1)))` (RFC 1321).
1598#[rustfmt::skip]
1599const MD5_K: [u32; 64] = [
1600    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
1601    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
1602    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
1603    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
1604    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
1605    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
1606    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
1607    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
1608];
1609
1610/// One-shot MD5 (RFC 1321). Sufficient for the small key-derivation inputs; not
1611/// a streaming API.
1612fn md5(input: &[u8]) -> [u8; 16] {
1613    let (mut a0, mut b0, mut c0, mut d0) = (
1614        0x6745_2301u32,
1615        0xefcd_ab89u32,
1616        0x98ba_dcfeu32,
1617        0x1032_5476u32,
1618    );
1619
1620    let mut msg = input.to_vec();
1621    let bitlen = (input.len() as u64).wrapping_mul(8);
1622    msg.push(0x80);
1623    while msg.len() % 64 != 56 {
1624        msg.push(0);
1625    }
1626    msg.extend_from_slice(&bitlen.to_le_bytes());
1627
1628    for chunk in msg.as_chunks::<64>().0 {
1629        let mut m = [0u32; 16];
1630        for (word, bytes) in m.iter_mut().zip(chunk.as_chunks::<4>().0) {
1631            *word = u32::from_le_bytes(*bytes);
1632        }
1633        let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
1634        for i in 0..64 {
1635            let (f, g) = match i {
1636                0..=15 => ((b & c) | (!b & d), i),
1637                16..=31 => ((d & b) | (!d & c), (5 * i + 1) % 16),
1638                32..=47 => (b ^ c ^ d, (3 * i + 5) % 16),
1639                _ => (c ^ (b | !d), (7 * i) % 16),
1640            };
1641            let f = f.wrapping_add(a).wrapping_add(MD5_K[i]).wrapping_add(m[g]);
1642            a = d;
1643            d = c;
1644            c = b;
1645            b = b.wrapping_add(f.rotate_left(MD5_S[i]));
1646        }
1647        a0 = a0.wrapping_add(a);
1648        b0 = b0.wrapping_add(b);
1649        c0 = c0.wrapping_add(c);
1650        d0 = d0.wrapping_add(d);
1651    }
1652
1653    let mut out = [0u8; 16];
1654    out[0..4].copy_from_slice(&a0.to_le_bytes());
1655    out[4..8].copy_from_slice(&b0.to_le_bytes());
1656    out[8..12].copy_from_slice(&c0.to_le_bytes());
1657    out[12..16].copy_from_slice(&d0.to_le_bytes());
1658    out
1659}
1660
1661#[cfg(test)]
1662mod tests {
1663    use super::*;
1664    use crate::object::Name;
1665
1666    fn hex(bytes: &[u8]) -> String {
1667        bytes.iter().map(|b| format!("{b:02x}")).collect()
1668    }
1669
1670    #[test]
1671    fn md5_known_vectors() {
1672        assert_eq!(hex(&md5(b"")), "d41d8cd98f00b204e9800998ecf8427e");
1673        assert_eq!(hex(&md5(b"abc")), "900150983cd24fb0d6963f7d28e17f72");
1674        assert_eq!(
1675            hex(&md5(b"The quick brown fox jumps over the lazy dog")),
1676            "9e107d9d372bb6826bd81d3542a419d6"
1677        );
1678    }
1679
1680    #[test]
1681    fn md5_spans_block_boundary() {
1682        // 56 bytes forces a second padded block.
1683        let input = [b'a'; 56];
1684        assert_eq!(hex(&md5(&input)), "3b0c8ac703f828b04c6c197006d17218");
1685    }
1686
1687    /// FIPS-197 Appendix C block vectors, both key sizes and both
1688    /// directions, held against the dispatching entry (the hardware path
1689    /// wherever this test runs on a CPU with AES instructions) AND the
1690    /// portable rounds directly, so neither path can drift.
1691    #[test]
1692    fn aes_block_known_vectors() {
1693        let plain: [u8; 16] = core::array::from_fn(|i| (i as u8) * 0x11);
1694        let key128: [u8; 16] = core::array::from_fn(|i| i as u8);
1695        let key256: [u8; 32] = core::array::from_fn(|i| i as u8);
1696
1697        type Encrypt = fn(&mut [u8; 16], &[[u8; 16]]);
1698        type Decrypt = fn(&mut [u8; 16], &[[u8; 16]], &[u8; 256]);
1699        let paths: [(Encrypt, Decrypt); 2] = [
1700            (aes_encrypt_block, aes_decrypt_block),
1701            (aes_encrypt_block_soft, aes_decrypt_block_soft),
1702        ];
1703        for (encrypt, decrypt) in paths {
1704            let mut s = plain;
1705            encrypt(&mut s, &aes_expand_key(&key128));
1706            assert_eq!(hex(&s), "69c4e0d86a7b0430d8cdb78070b4c55a");
1707            decrypt(&mut s, &aes_expand_key(&key128), &aes_inv_sbox());
1708            assert_eq!(s, plain);
1709
1710            let mut s = plain;
1711            encrypt(&mut s, &aes_expand_key(&key256));
1712            assert_eq!(hex(&s), "8ea2b7ca516745bfeafc49904b496089");
1713            decrypt(&mut s, &aes_expand_key(&key256), &aes_inv_sbox());
1714            assert_eq!(s, plain);
1715        }
1716    }
1717
1718    /// Multi-block CBC round-trips through the block paths, chaining
1719    /// included: what the R6 hash encrypts, decryption must invert.
1720    #[test]
1721    fn aes_cbc_round_trips_multiple_blocks() {
1722        let key: [u8; 16] = core::array::from_fn(|i| (i as u8).wrapping_mul(7));
1723        let iv: [u8; 16] = core::array::from_fn(|i| 0xa5 ^ (i as u8));
1724        let data: Vec<u8> = (0..64u8).collect();
1725        let ct = aes_cbc_encrypt_blocks(&key, &iv, &data);
1726        assert_eq!(ct.len(), data.len());
1727        assert_ne!(ct, data);
1728        assert_eq!(aes_cbc_decrypt_blocks(&key, &iv, &ct), data);
1729    }
1730
1731    /// NIST FIPS 180 vectors for the SHA-2 family, spanning the empty
1732    /// input, one block, and inputs long enough to cross block
1733    /// boundaries — the contract the hardware compressions must match.
1734    #[test]
1735    fn sha2_known_vectors() {
1736        assert_eq!(
1737            hex(&sha256(b"")),
1738            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
1739        );
1740        assert_eq!(
1741            hex(&sha256(b"abc")),
1742            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
1743        );
1744        assert_eq!(
1745            hex(&sha256(&[b'a'; 200])),
1746            "c2a908d98f5df987ade41b5fce213067efbcc21ef2240212a41e54b5e7c28ae5"
1747        );
1748        assert_eq!(
1749            hex(&sha384(b"abc")),
1750            "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed\
1751             8086072ba1e7cc2358baeca134c825a7"
1752        );
1753        assert_eq!(
1754            hex(&sha512(b"abc")),
1755            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
1756             2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
1757        );
1758        assert_eq!(
1759            hex(&sha512(&[b'a'; 300])),
1760            "a6a77010dd9696c23831e6549de51724df332c2075039b75fcfe6c2e6de42fbd\
1761             3c80ed4073267e00c8c320712c3cdd9d65a96f90a3fe4a58a6b70a103be08e83"
1762        );
1763    }
1764
1765    /// The portable compressions must evolve state identically to the
1766    /// dispatching entries (the hardware path wherever this runs on a CPU
1767    /// with SHA instructions), block by block.
1768    #[test]
1769    fn sha2_soft_compressions_match_the_dispatch() {
1770        let block64: [u8; 64] = core::array::from_fn(|i| (i as u8).wrapping_mul(31));
1771        let mut via_dispatch = [0x6a09e667u32; 8];
1772        let mut via_soft = via_dispatch;
1773        sha256_compress(&mut via_dispatch, &block64);
1774        sha256_compress_soft(&mut via_soft, &block64);
1775        assert_eq!(via_dispatch, via_soft);
1776
1777        let block128: [u8; 128] = core::array::from_fn(|i| (i as u8).wrapping_mul(29));
1778        let mut via_dispatch = [0x6a09e667f3bcc908u64; 8];
1779        let mut via_soft = via_dispatch;
1780        sha512_compress(&mut via_dispatch, &block128);
1781        sha512_compress_soft(&mut via_soft, &block128);
1782        assert_eq!(via_dispatch, via_soft);
1783    }
1784
1785    /// The incremental hasher must match the one-shot digest whatever the
1786    /// slice boundaries: odd sizes, block-straddling feeds, empty updates.
1787    #[test]
1788    fn incremental_sha256_matches_the_one_shot() {
1789        let input: Vec<u8> = (0..300u16).map(|i| (i % 251) as u8).collect();
1790        for splits in [
1791            vec![0, 1, 3, 7, 60, 63, 64, 65, 37],
1792            vec![300],
1793            vec![128, 128, 44],
1794            vec![55, 9, 236],
1795        ] {
1796            let mut hasher = Sha256::new();
1797            let mut fed = 0usize;
1798            for len in splits {
1799                hasher.update(&input[fed..fed + len]);
1800                fed += len;
1801            }
1802            hasher.update(&input[fed..]);
1803            assert_eq!(hasher.finalize(), sha256(&input));
1804        }
1805        assert_eq!(Sha256::new().finalize(), sha256(b""));
1806        assert_eq!(Sha256::default().finalize(), sha256(b""));
1807    }
1808
1809    #[test]
1810    fn rc4_known_vector() {
1811        // Classic RC4 test vector: key "Key", plaintext "Plaintext".
1812        let ct = rc4(b"Key", b"Plaintext");
1813        assert_eq!(hex(&ct), "bbf316e8d940af0ad3");
1814        // Symmetric: decrypting the ciphertext returns the plaintext.
1815        assert_eq!(rc4(b"Key", &ct), b"Plaintext");
1816    }
1817
1818    // --- End-to-end fixture: build a V2/R3 (128-bit RC4) file encrypted under
1819    // caller-chosen user and owner passwords, then confirm the loader
1820    // decrypts it — transparently for the empty user password, and through
1821    // the password APIs for real ones. ---
1822
1823    const N: usize = 16; // 128-bit key
1824    const P: i32 = -44;
1825    const ID0: &[u8] = b"0123456789abcdef";
1826
1827    /// `/O` for the given owner and user passwords (Algorithm 3, R3).
1828    fn owner_entry(owner_pw: &[u8], user_pw: &[u8]) -> Vec<u8> {
1829        let mut d = md5(&pad_password(owner_pw));
1830        for _ in 0..50 {
1831            d = md5(&d[..N]);
1832        }
1833        let rc4key = d[..N].to_vec();
1834        let mut o = rc4(&rc4key, &pad_password(user_pw));
1835        for i in 1u8..=19 {
1836            let k: Vec<u8> = rc4key.iter().map(|b| b ^ i).collect();
1837            o = rc4(&k, &o);
1838        }
1839        o
1840    }
1841
1842    /// File key from `/O` for the given user password (Algorithm 2, R3).
1843    fn file_key(o: &[u8], user_pw: &[u8]) -> Vec<u8> {
1844        let mut input = Vec::new();
1845        input.extend_from_slice(&pad_password(user_pw));
1846        input.extend_from_slice(o);
1847        input.extend_from_slice(&(P as u32).to_le_bytes());
1848        input.extend_from_slice(ID0);
1849        let mut d = md5(&input);
1850        for _ in 0..50 {
1851            d = md5(&d[..N]);
1852        }
1853        d[..N].to_vec()
1854    }
1855
1856    /// `/U` for the given file key (Algorithm 5, R3).
1857    fn user_entry(key: &[u8]) -> Vec<u8> {
1858        let mut input = Vec::new();
1859        input.extend_from_slice(&PAD);
1860        input.extend_from_slice(ID0);
1861        let mut x = md5(&input).to_vec();
1862        x = rc4(key, &x);
1863        for i in 1u8..=19 {
1864            let k: Vec<u8> = key.iter().map(|b| b ^ i).collect();
1865            x = rc4(&k, &x);
1866        }
1867        x.resize(32, 0); // trailing padding is arbitrary
1868        x
1869    }
1870
1871    fn obj_key(key: &[u8], num: u32, gen: u16) -> Vec<u8> {
1872        let mut input = key.to_vec();
1873        input.extend_from_slice(&num.to_le_bytes()[..3]);
1874        input.extend_from_slice(&gen.to_le_bytes()[..2]);
1875        md5(&input)[..(key.len() + 5).min(16)].to_vec()
1876    }
1877
1878    fn hexstr(b: &[u8]) -> String {
1879        let mut s = String::from("<");
1880        for x in b {
1881            s.push_str(&format!("{x:02x}"));
1882        }
1883        s.push('>');
1884        s
1885    }
1886
1887    fn encrypted_fixture(u_override: Option<Vec<u8>>) -> Vec<u8> {
1888        encrypted_fixture_with(b"", b"", u_override)
1889    }
1890
1891    fn encrypted_fixture_with(
1892        user_pw: &[u8],
1893        owner_pw: &[u8],
1894        u_override: Option<Vec<u8>>,
1895    ) -> Vec<u8> {
1896        use pdfboss_testkit::PdfBuilder;
1897        let o = owner_entry(owner_pw, user_pw);
1898        let key = file_key(&o, user_pw);
1899        let u = u_override.unwrap_or_else(|| user_entry(&key));
1900
1901        let msg = rc4(&obj_key(&key, 3, 0), b"Top secret message");
1902        let stream = rc4(&obj_key(&key, 4, 0), b"decrypted stream body");
1903
1904        let mut b = PdfBuilder::new().version(1, 4);
1905        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1906        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1907        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
1908        b.stream(4, "", &stream);
1909        b.object(
1910            9,
1911            &format!(
1912                "<< /Filter /Standard /V 2 /R 3 /Length 128 /P {P} /O {} /U {} >>",
1913                hexstr(&o),
1914                hexstr(&u)
1915            ),
1916        );
1917        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
1918        b.trailer_extra(&trailer).build(1)
1919    }
1920
1921    #[test]
1922    fn document_load_decrypts_standard_rc4() {
1923        use crate::object::ObjRef;
1924        use crate::Document;
1925
1926        let doc = Document::load(encrypted_fixture(None)).expect("empty password opens the file");
1927
1928        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1929        let msg = obj3
1930            .as_dict()
1931            .unwrap()
1932            .get("Msg")
1933            .unwrap()
1934            .as_str_bytes()
1935            .unwrap();
1936        assert_eq!(msg, b"Top secret message", "string decrypted");
1937
1938        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
1939        let data = doc.stream_data(obj4.as_stream().unwrap()).unwrap();
1940        assert_eq!(data, b"decrypted stream body", "stream decrypted");
1941    }
1942
1943    #[test]
1944    fn document_load_rejects_when_password_does_not_verify() {
1945        use crate::error::Error;
1946        use crate::Document;
1947
1948        // A `/U` that will not verify under the empty password stands in for a
1949        // real password-protected file: the loader must decline, not decrypt.
1950        let bad_u = vec![0u8; 32];
1951        let err = Document::load(encrypted_fixture(Some(bad_u)));
1952        assert!(matches!(err, Err(Error::Encrypted)));
1953    }
1954
1955    #[test]
1956    fn real_user_password_opens_an_rc4_file() {
1957        use crate::error::Error;
1958        use crate::object::ObjRef;
1959        use crate::Document;
1960
1961        let bytes = encrypted_fixture_with(b"hunter2", b"owner-secret", None);
1962        assert!(
1963            matches!(Document::load(bytes.clone()), Err(Error::Encrypted)),
1964            "without the password the file stays closed"
1965        );
1966        let doc = Document::load_with_password(bytes, "hunter2").expect("user password opens");
1967        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1968        let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
1969        assert_eq!(msg.as_str_bytes().unwrap(), b"Top secret message");
1970    }
1971
1972    #[test]
1973    fn owner_password_opens_an_rc4_file() {
1974        use crate::object::ObjRef;
1975        use crate::Document;
1976
1977        let bytes = encrypted_fixture_with(b"hunter2", b"owner-secret", None);
1978        let doc =
1979            Document::load_with_password(bytes, "owner-secret").expect("owner password opens");
1980        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1981        let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
1982        assert_eq!(msg.as_str_bytes().unwrap(), b"Top secret message");
1983    }
1984
1985    #[test]
1986    fn wrong_password_stays_encrypted() {
1987        use crate::error::Error;
1988        use crate::Document;
1989
1990        let bytes = encrypted_fixture_with(b"hunter2", b"owner-secret", None);
1991        let err = Document::load_with_password(bytes, "letmein");
1992        assert!(matches!(err, Err(Error::Encrypted)));
1993    }
1994
1995    #[test]
1996    fn real_passwords_open_an_aes256_r6_file() {
1997        use crate::error::Error;
1998        use crate::object::ObjRef;
1999        use crate::Document;
2000
2001        let bytes = encrypted_fixture_aesv3_with(6, "pässword".as_bytes(), b"owner-secret");
2002        assert!(
2003            matches!(Document::load(bytes.clone()), Err(Error::Encrypted)),
2004            "without the password the file stays closed"
2005        );
2006        for pw in ["pässword", "owner-secret"] {
2007            let doc = Document::load_with_password(bytes.clone(), pw)
2008                .unwrap_or_else(|_| panic!("{pw:?} opens the file"));
2009            let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
2010            let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
2011            assert_eq!(msg.as_str_bytes().unwrap(), b"AES-256 secret");
2012        }
2013        assert!(matches!(
2014            Document::load_with_password(bytes, "letmein"),
2015            Err(Error::Encrypted)
2016        ));
2017    }
2018
2019    #[test]
2020    fn empty_password_files_still_open_through_the_password_api() {
2021        use crate::object::ObjRef;
2022        use crate::Document;
2023
2024        // Passing a password to an empty-password file must not break it:
2025        // the empty user password still verifies... only if the caller's
2026        // password IS empty; a random one is simply wrong for this file.
2027        let doc = Document::load_with_password(encrypted_fixture(None), "")
2028            .expect("the empty password opens through the password API too");
2029        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
2030        let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
2031        assert_eq!(msg.as_str_bytes().unwrap(), b"Top secret message");
2032    }
2033
2034    #[test]
2035    fn unsupported_handler_is_declined() {
2036        // A future/unknown handler version is declined so the caller reports the
2037        // file as encrypted-and-unsupported.
2038        let mut enc = Dict::new();
2039        enc.insert(Name("Filter".into()), Object::Name(Name("Standard".into())));
2040        enc.insert(Name("V".into()), Object::Int(6));
2041        enc.insert(Name("R".into()), Object::Int(7));
2042        enc.insert(Name("O".into()), Object::String(vec![0; 48]));
2043        enc.insert(Name("U".into()), Object::String(vec![0; 48]));
2044        enc.insert(Name("P".into()), Object::Int(-4));
2045        assert!(Decryptor::from_standard(&enc, ID0).is_none());
2046    }
2047
2048    // --- AES / SHA-2 known-answer vectors ---
2049
2050    #[test]
2051    fn aes_fips197_block_vectors() {
2052        // FIPS-197 Appendix C.1 (AES-128) and C.3 (AES-256), same plaintext.
2053        let pt: [u8; 16] = [
2054            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
2055            0xee, 0xff,
2056        ];
2057        let key128: Vec<u8> = (0u8..16).collect();
2058        let rks = aes_expand_key(&key128);
2059        let mut b = pt;
2060        aes_encrypt_block(&mut b, &rks);
2061        assert_eq!(hex(&b), "69c4e0d86a7b0430d8cdb78070b4c55a");
2062        aes_decrypt_block(&mut b, &rks, &aes_inv_sbox());
2063        assert_eq!(b, pt, "AES-128 decrypt inverts encrypt");
2064
2065        let key256: Vec<u8> = (0u8..32).collect();
2066        let rks = aes_expand_key(&key256);
2067        let mut b = pt;
2068        aes_encrypt_block(&mut b, &rks);
2069        assert_eq!(hex(&b), "8ea2b7ca516745bfeafc49904b496089");
2070    }
2071
2072    #[test]
2073    fn sha2_vectors() {
2074        assert_eq!(
2075            hex(&sha256(b"abc")),
2076            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
2077        );
2078        assert_eq!(
2079            hex(&sha512(b"abc")),
2080            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
2081             2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
2082        );
2083        assert_eq!(
2084            hex(&sha384(b"abc")),
2085            "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed\
2086             8086072ba1e7cc2358baeca134c825a7"
2087        );
2088    }
2089
2090    #[test]
2091    fn aes_cbc_roundtrip() {
2092        let key: Vec<u8> = (0u8..16).collect();
2093        let iv = [0x24u8; 16];
2094        let pt = b"a message spanning several AES blocks exactly?!!";
2095        let ct = aes_cbc_encrypt_blocks(&key, &iv, &pkcs7_pad(pt));
2096        let mut val = iv.to_vec(); // PDF format: IV followed by ciphertext
2097        val.extend_from_slice(&ct);
2098        assert_eq!(aes_cbc_decrypt(&key, &val), pt);
2099    }
2100
2101    // --- AESV2 (V4/R4) end-to-end fixture ---
2102
2103    fn obj_key_aes(key: &[u8], num: u32, gen: u16) -> Vec<u8> {
2104        let mut input = key.to_vec();
2105        input.extend_from_slice(&num.to_le_bytes()[..3]);
2106        input.extend_from_slice(&gen.to_le_bytes()[..2]);
2107        input.extend_from_slice(b"sAlT");
2108        md5(&input)[..(key.len() + 5).min(16)].to_vec()
2109    }
2110
2111    /// Encrypts as a PDF AES value: a 16-byte IV followed by CBC ciphertext of
2112    /// the PKCS#7-padded plaintext.
2113    fn aes_encrypt_pdf(key: &[u8], pt: &[u8], iv: &[u8; 16]) -> Vec<u8> {
2114        let mut out = iv.to_vec();
2115        out.extend_from_slice(&aes_cbc_encrypt_blocks(key, iv, &pkcs7_pad(pt)));
2116        out
2117    }
2118
2119    fn encrypted_fixture_aesv2() -> Vec<u8> {
2120        use pdfboss_testkit::PdfBuilder;
2121        let o = owner_entry(b"", b"");
2122        let key = file_key(&o, b""); // R4 derivation matches R3 (EncryptMetadata true)
2123        let u = user_entry(&key);
2124        let iv = [0x11u8; 16];
2125        let msg = aes_encrypt_pdf(&obj_key_aes(&key, 3, 0), b"Top secret message", &iv);
2126        let stream = aes_encrypt_pdf(&obj_key_aes(&key, 4, 0), b"decrypted stream body", &iv);
2127
2128        let mut b = PdfBuilder::new().version(1, 5);
2129        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
2130        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
2131        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
2132        b.stream(4, "", &stream);
2133        b.object(
2134            9,
2135            &format!(
2136                "<< /Filter /Standard /V 4 /R 4 /Length 128 /P {P} /O {} /U {} \
2137                 /CF << /StdCF << /CFM /AESV2 /Length 16 >> >> /StmF /StdCF /StrF /StdCF >>",
2138                hexstr(&o),
2139                hexstr(&u)
2140            ),
2141        );
2142        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
2143        b.trailer_extra(&trailer).build(1)
2144    }
2145
2146    #[test]
2147    fn document_load_decrypts_aesv2() {
2148        use crate::object::ObjRef;
2149        use crate::Document;
2150        let doc = Document::load(encrypted_fixture_aesv2()).expect("AESV2 empty password opens");
2151        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
2152        let msg = obj3
2153            .as_dict()
2154            .unwrap()
2155            .get("Msg")
2156            .unwrap()
2157            .as_str_bytes()
2158            .unwrap();
2159        assert_eq!(msg, b"Top secret message");
2160        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
2161        assert_eq!(
2162            doc.stream_data(obj4.as_stream().unwrap()).unwrap(),
2163            b"decrypted stream body"
2164        );
2165    }
2166
2167    // --- AESV3 (V5/R5 and R6) end-to-end fixture ---
2168
2169    fn encrypted_fixture_aesv3(r: i64) -> Vec<u8> {
2170        encrypted_fixture_aesv3_with(r, b"", b"")
2171    }
2172
2173    fn encrypted_fixture_aesv3_with(r: i64, user_pw: &[u8], owner_pw: &[u8]) -> Vec<u8> {
2174        encrypted_fixture_aesv3_with_metadata(r, user_pw, owner_pw, true, None)
2175    }
2176
2177    /// [`encrypted_fixture_aesv3_with`], plus an `/EncryptMetadata` entry
2178    /// and, when `metadata` is given, object 5 as a `/Type /Metadata`
2179    /// stream holding its bytes verbatim (never encrypted here, since a
2180    /// real writer with `encrypt_metadata` false stores it in plaintext
2181    /// too): the fixture a reader must not corrupt.
2182    fn encrypted_fixture_aesv3_with_metadata(
2183        r: i64,
2184        user_pw: &[u8],
2185        owner_pw: &[u8],
2186        encrypt_metadata: bool,
2187        metadata: Option<&[u8]>,
2188    ) -> Vec<u8> {
2189        use pdfboss_testkit::PdfBuilder;
2190        let key: Vec<u8> = (0u8..32).map(|i| i ^ 0x5a).collect(); // arbitrary 256-bit file key
2191        let vsalt: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
2192        let ksalt: [u8; 8] = [9, 10, 11, 12, 13, 14, 15, 16];
2193        let mut u = hash_2b(r, user_pw, &vsalt, &[]); // 32-byte validation hash
2194        u.extend_from_slice(&vsalt);
2195        u.extend_from_slice(&ksalt);
2196        let intermediate = hash_2b(r, user_pw, &ksalt, &[]);
2197        let ue = aes_cbc_encrypt_blocks(&intermediate, &[0u8; 16], &key);
2198        // The owner hashes additionally salt in the first 48 bytes of /U.
2199        let ovsalt: [u8; 8] = [21, 22, 23, 24, 25, 26, 27, 28];
2200        let oksalt: [u8; 8] = [31, 32, 33, 34, 35, 36, 37, 38];
2201        let mut o = hash_2b(r, owner_pw, &ovsalt, &u[..48]);
2202        o.extend_from_slice(&ovsalt);
2203        o.extend_from_slice(&oksalt);
2204        let ointermediate = hash_2b(r, owner_pw, &oksalt, &u[..48]);
2205        let oe = aes_cbc_encrypt_blocks(&ointermediate, &[0u8; 16], &key);
2206        let iv = [0x22u8; 16];
2207        let msg = aes_encrypt_pdf(&key, b"AES-256 secret", &iv);
2208        let stream = aes_encrypt_pdf(&key, b"AES-256 stream body", &iv);
2209
2210        let mut b = PdfBuilder::new().version(1, 7);
2211        let catalog = match metadata {
2212            Some(_) => "<< /Type /Catalog /Pages 2 0 R /Metadata 5 0 R >>",
2213            None => "<< /Type /Catalog /Pages 2 0 R >>",
2214        };
2215        b.object(1, catalog);
2216        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
2217        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
2218        b.stream(4, "", &stream);
2219        if let Some(metadata) = metadata {
2220            b.stream(5, "<< /Type /Metadata /Subtype /XML >>", metadata);
2221        }
2222        let encrypt_metadata_entry = if encrypt_metadata {
2223            ""
2224        } else {
2225            " /EncryptMetadata false"
2226        };
2227        b.object(
2228            9,
2229            &format!(
2230                "<< /Filter /Standard /V 5 /R {r} /Length 256 /P {P} /U {} /UE {} \
2231                 /O {} /OE {}{encrypt_metadata_entry} \
2232                 /CF << /StdCF << /CFM /AESV3 /Length 32 >> >> /StmF /StdCF /StrF /StdCF >>",
2233                hexstr(&u),
2234                hexstr(&ue),
2235                hexstr(&o),
2236                hexstr(&oe)
2237            ),
2238        );
2239        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
2240        b.trailer_extra(&trailer).build(1)
2241    }
2242
2243    fn assert_aesv3_decrypts(r: i64) {
2244        use crate::object::ObjRef;
2245        use crate::Document;
2246        let doc = Document::load(encrypted_fixture_aesv3(r)).expect("AESV3 empty password opens");
2247        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
2248        let msg = obj3
2249            .as_dict()
2250            .unwrap()
2251            .get("Msg")
2252            .unwrap()
2253            .as_str_bytes()
2254            .unwrap();
2255        assert_eq!(msg, b"AES-256 secret", "R{r} string");
2256        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
2257        assert_eq!(
2258            doc.stream_data(obj4.as_stream().unwrap()).unwrap(),
2259            b"AES-256 stream body",
2260            "R{r} stream"
2261        );
2262    }
2263
2264    #[test]
2265    fn document_load_decrypts_aesv3_r5() {
2266        assert_aesv3_decrypts(5);
2267    }
2268
2269    #[test]
2270    fn document_load_decrypts_aesv3_r6() {
2271        assert_aesv3_decrypts(6); // exercises the iterated Algorithm 2.B hash
2272    }
2273
2274    #[test]
2275    fn encrypt_metadata_false_leaves_the_metadata_stream_plaintext() {
2276        use crate::object::ObjRef;
2277        use crate::Document;
2278
2279        const XMP: &[u8] = b"<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>\
2280            <x:xmpmeta xmlns:x='adobe:ns:meta/'><rdf:RDF \
2281            xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'><rdf:Description \
2282            xmlns:dc='http://purl.org/dc/elements/1.1/'><dc:title>XMP Secret \
2283            Title</dc:title></rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end='w'?>";
2284
2285        let bytes =
2286            encrypted_fixture_aesv3_with_metadata(6, b"user-pw", b"owner-pw", false, Some(XMP));
2287        let doc = Document::load_with_password(bytes, "user-pw").expect("user password opens");
2288
2289        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
2290        let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
2291        assert_eq!(
2292            msg.as_str_bytes().unwrap(),
2293            b"AES-256 secret",
2294            "an ordinary string still decrypts under EncryptMetadata false"
2295        );
2296
2297        let obj5 = doc.get(ObjRef { num: 5, gen: 0 }).unwrap();
2298        let metadata = doc.stream_data(obj5.as_stream().unwrap()).unwrap();
2299        assert_eq!(
2300            metadata, XMP,
2301            "the metadata stream was stored in plaintext and must not be decrypted"
2302        );
2303    }
2304
2305    // --- R6 key material (write side): the existing validation code above
2306    // is the oracle everything generated here must open under. ---
2307
2308    /// Generated key material must open through the same `aesv3_key`
2309    /// validation a real reader runs, for both passwords, and reject a
2310    /// wrong one; the embedded `/Perms` must decrypt to its marker under
2311    /// the generated file key. The dict comes from `aes256_encrypt_dict`,
2312    /// the same builder `Encryptor::aes256_with_rng` ships, so this test
2313    /// exercises the real production layout rather than a hand-built one.
2314    #[test]
2315    fn r6_key_material_opens_under_aesv3_key() {
2316        let mut c = 0u8;
2317        let mut rng = |b: &mut [u8]| {
2318            for x in b {
2319                c = c.wrapping_add(1);
2320                *x = c;
2321            }
2322        };
2323        let material = r6_key_material(b"user-pw", b"owner-pw", P, true, &mut rng);
2324        let dict = aes256_encrypt_dict(&material, P);
2325
2326        let user_key = aesv3_key(&dict, 6, b"user-pw").expect("user password opens");
2327        assert_eq!(user_key, material.file_key.to_vec());
2328
2329        let owner_key = aesv3_key(&dict, 6, b"owner-pw").expect("owner password opens");
2330        assert_eq!(owner_key, material.file_key.to_vec());
2331
2332        assert!(aesv3_key(&dict, 6, b"wrong-pw").is_none());
2333
2334        assert!(perms_marker_valid(&material.file_key, &material.perms));
2335
2336        // With /Perms absent, aesv3_key skips the fast path and falls
2337        // through to comparing the /U and /O validation hashes directly,
2338        // proving those 32 bytes (not just the key-salt derivation the
2339        // fast path above exercised) are correct too.
2340        let mut without_perms = dict.clone();
2341        without_perms.remove("Perms");
2342        assert_eq!(
2343            aesv3_key(&without_perms, 6, b"user-pw"),
2344            Some(material.file_key.to_vec())
2345        );
2346        assert_eq!(
2347            aesv3_key(&without_perms, 6, b"owner-pw"),
2348            Some(material.file_key.to_vec())
2349        );
2350    }
2351
2352    // --- Permissions: the /P bit layout (ISO 32000-2 Table 22) ---
2353
2354    #[test]
2355    fn permissions_all_grants_every_bit() {
2356        // Bits 1-2 reserved and cleared, every other bit set: -4.
2357        assert_eq!(Permissions::all().p_value(), -4);
2358        assert_eq!(Permissions::default().p_value(), -4);
2359    }
2360
2361    #[test]
2362    fn permissions_p_value_clears_exactly_its_own_bit() {
2363        let base = Permissions::all().p_value();
2364        assert_eq!(
2365            Permissions {
2366                print: false,
2367                ..Permissions::all()
2368            }
2369            .p_value(),
2370            base & !(1 << 2), // bit 3
2371        );
2372        assert_eq!(
2373            Permissions {
2374                modify: false,
2375                ..Permissions::all()
2376            }
2377            .p_value(),
2378            base & !(1 << 3), // bit 4
2379        );
2380        assert_eq!(
2381            Permissions {
2382                copy: false,
2383                ..Permissions::all()
2384            }
2385            .p_value(),
2386            base & !(1 << 4), // bit 5
2387        );
2388        assert_eq!(
2389            Permissions {
2390                annotate: false,
2391                ..Permissions::all()
2392            }
2393            .p_value(),
2394            base & !(1 << 5), // bit 6
2395        );
2396        assert_eq!(
2397            Permissions {
2398                fill_forms: false,
2399                ..Permissions::all()
2400            }
2401            .p_value(),
2402            base & !(1 << 8), // bit 9
2403        );
2404        assert_eq!(
2405            Permissions {
2406                accessibility: false,
2407                ..Permissions::all()
2408            }
2409            .p_value(),
2410            base & !(1 << 9), // bit 10
2411        );
2412        assert_eq!(
2413            Permissions {
2414                assemble: false,
2415                ..Permissions::all()
2416            }
2417            .p_value(),
2418            base & !(1 << 10), // bit 11
2419        );
2420        assert_eq!(
2421            Permissions {
2422                print_hires: false,
2423                ..Permissions::all()
2424            }
2425            .p_value(),
2426            base & !(1 << 11), // bit 12
2427        );
2428    }
2429
2430    // --- Encryptor (write side): the round-trip proof for Task 2 ---
2431
2432    /// A `FnMut(&mut [u8])` filling buffers from an incrementing counter,
2433    /// so key material and IVs are reproducible without touching the OS
2434    /// random source.
2435    #[allow(clippy::type_complexity, reason = "see Encryptor's rng field")]
2436    fn counter_rng() -> Box<dyn FnMut(&mut [u8]) + Send> {
2437        let mut c = 0u8;
2438        Box::new(move |buf: &mut [u8]| {
2439            for b in buf {
2440                c = c.wrapping_add(1);
2441                *b = c;
2442            }
2443        })
2444    }
2445
2446    /// Recovers the file key from a produced `/Encrypt` dict through the
2447    /// same `aesv3_key` validation a real reader runs, and builds the
2448    /// matching `Decryptor`: the oracle every encrypted object must open
2449    /// under.
2450    fn decryptor_for(dict: &Dict, password: &[u8]) -> Decryptor {
2451        let key = aesv3_key(dict, 6, password).expect("password opens the produced dict");
2452        Decryptor {
2453            key,
2454            cipher: Cipher::Aesv3,
2455            encrypt_metadata: true,
2456        }
2457    }
2458
2459    #[test]
2460    fn encrypt_object_round_trips_dict_array_and_leaves_other_types_untouched() {
2461        use crate::object::ObjRef;
2462
2463        let (mut enc, dict) =
2464            Encryptor::aes256_with_rng("user-pw", "owner-pw", Permissions::all(), counter_rng());
2465
2466        let mut d = Dict::new();
2467        d.insert(
2468            Name("Msg".to_string()),
2469            Object::String(b"Top secret message".to_vec()),
2470        );
2471        d.insert(
2472            Name("List".to_string()),
2473            Object::Array(vec![
2474                Object::String(b"first".to_vec()),
2475                Object::String(b"second".to_vec()),
2476            ]),
2477        );
2478        d.insert(
2479            Name("Ref".to_string()),
2480            Object::Ref(ObjRef { num: 7, gen: 0 }),
2481        );
2482        d.insert(
2483            Name("Kind".to_string()),
2484            Object::Name(Name("Example".to_string())),
2485        );
2486        d.insert(Name("Count".to_string()), Object::Int(42));
2487        d.insert(Name("Scale".to_string()), Object::Real(1.5));
2488        d.insert(Name("On".to_string()), Object::Bool(true));
2489        d.insert(Name("Missing".to_string()), Object::Null);
2490        let original = Object::Dict(d);
2491
2492        let mut obj = original.clone();
2493        enc.encrypt_object(&mut obj, 3, 0);
2494        assert_ne!(obj, original, "encryption must change the strings");
2495
2496        // Ref, Name, numbers, bools and null pass through untouched, even
2497        // in the still-encrypted object.
2498        let (enc_dict, orig_dict) = (obj.as_dict().unwrap(), original.as_dict().unwrap());
2499        for key in ["Ref", "Kind", "Count", "Scale", "On", "Missing"] {
2500            assert_eq!(
2501                enc_dict.get(key),
2502                orig_dict.get(key),
2503                "{key} passes through untouched"
2504            );
2505        }
2506
2507        let decryptor = decryptor_for(&dict, b"user-pw");
2508        let mut roundtripped = obj.clone();
2509        decryptor.decrypt_object(&mut roundtripped, 3, 0);
2510        assert_eq!(roundtripped, original);
2511
2512        // The owner password recovers the same file key.
2513        let owner_decryptor = decryptor_for(&dict, b"owner-pw");
2514        let mut via_owner = obj;
2515        owner_decryptor.decrypt_object(&mut via_owner, 3, 0);
2516        assert_eq!(via_owner, original);
2517    }
2518
2519    #[test]
2520    fn encrypt_object_round_trips_a_stream_and_its_dict() {
2521        use crate::object::Stream;
2522
2523        let (mut enc, dict) =
2524            Encryptor::aes256_with_rng("", "owner-pw", Permissions::all(), counter_rng());
2525
2526        let mut stream_dict = Dict::new();
2527        stream_dict.insert(
2528            Name("Producer".to_string()),
2529            Object::String(b"pdfboss".to_vec()),
2530        );
2531        let original = Object::Stream(Stream {
2532            dict: stream_dict,
2533            data: b"decrypted stream body".to_vec(),
2534        });
2535        let mut obj = original.clone();
2536        enc.encrypt_object(&mut obj, 4, 0);
2537        let encrypted = obj.as_stream().unwrap();
2538        assert_ne!(encrypted.data, original.as_stream().unwrap().data);
2539        assert_ne!(
2540            encrypted.dict.get("Producer"),
2541            original.as_stream().unwrap().dict.get("Producer"),
2542            "the stream dict's own strings are encrypted too"
2543        );
2544
2545        let decryptor = decryptor_for(&dict, b"");
2546        let mut roundtripped = obj;
2547        decryptor.decrypt_object(&mut roundtripped, 4, 0);
2548        assert_eq!(roundtripped, original);
2549    }
2550
2551    #[test]
2552    fn encrypt_object_round_trips_padding_edge_lengths() {
2553        let (mut enc, dict) =
2554            Encryptor::aes256_with_rng("pw", "owner", Permissions::all(), counter_rng());
2555        let decryptor = decryptor_for(&dict, b"pw");
2556
2557        for plaintext in [Vec::new(), vec![0x41], vec![0x42; 16]] {
2558            let original = Object::String(plaintext.clone());
2559            let mut obj = original.clone();
2560            enc.encrypt_object(&mut obj, 1, 0);
2561            let mut roundtripped = obj;
2562            decryptor.decrypt_object(&mut roundtripped, 1, 0);
2563            assert_eq!(roundtripped, original, "{} bytes", plaintext.len());
2564        }
2565    }
2566}