Skip to main content

pdf_syntax/crypto/
mod.rs

1//! Cryptographic implementations for hayro, ported from pdf.js.
2//!
3//! **Important note**: Please keep in mind that these haven't been
4//! audited and should not be used for security-critical purposes, like creating new
5//! encrypted PDFs. They solely serve the purpose of being able to decrypt and read
6//! _already_ encrypted documents, where security isn't really relevant.
7
8use crate::crypto::DecryptionError::InvalidEncryption;
9use crate::crypto::aes::{AES128Cipher, AES256Cipher};
10use crate::crypto::rc4::Rc4;
11use crate::object;
12use crate::object::dict::keys::{
13    CF, CFM, ENCRYPT_META_DATA, FILTER, LENGTH, O, OE, P, R, STM_F, STR_F, U, UE, V,
14};
15use crate::object::{Dict, Name, ObjectIdentifier};
16use crate::sync::HashMap;
17use alloc::string::ToString;
18use alloc::vec;
19use alloc::vec::Vec;
20use core::cmp;
21use core::ops::Deref;
22
23mod aes;
24mod md5;
25mod rc4;
26mod sha256;
27mod sha384;
28mod sha512;
29
30const PASSWORD_PADDING: [u8; 32] = [
31    0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
32    0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
33];
34
35/// An error that occurred during decryption.
36#[derive(Debug, Copy, Clone, PartialEq, Eq)]
37pub enum DecryptionError {
38    /// The ID entry is missing in the PDF.
39    MissingIDEntry,
40    /// The PDF is password-protected and no password (or a wrong one) was
41    /// provided.
42    PasswordProtected,
43    /// The PDF has invalid encryption.
44    InvalidEncryption,
45    /// The PDF uses an unsupported encryption algorithm.
46    UnsupportedAlgorithm,
47}
48
49#[derive(Debug, Copy, Clone, PartialEq, Eq)]
50enum DecryptorTag {
51    None,
52    Rc4,
53    Aes128,
54    Aes256,
55}
56
57impl DecryptorTag {
58    fn from_name(name: &Name) -> Option<Self> {
59        match name.as_str() {
60            "None" | "Identity" => Some(Self::None),
61            "V2" => Some(Self::Rc4),
62            "AESV2" => Some(Self::Aes128),
63            "AESV3" => Some(Self::Aes256),
64            _ => None,
65        }
66    }
67}
68#[derive(Debug, Clone)]
69pub(crate) enum Decryptor {
70    None,
71    Rc4 { key: Vec<u8> },
72    Aes128 { key: Vec<u8>, dict: DecryptorData },
73    Aes256 { key: Vec<u8>, dict: DecryptorData },
74}
75
76#[derive(Debug, Copy, Clone)]
77pub(crate) enum DecryptionTarget {
78    String,
79    Stream,
80}
81
82impl Decryptor {
83    pub(crate) fn decrypt(
84        &self,
85        id: ObjectIdentifier,
86        data: &[u8],
87        target: DecryptionTarget,
88    ) -> Option<Vec<u8>> {
89        match self {
90            Self::None => Some(data.to_vec()),
91            Self::Rc4 { key } => decrypt_rc4(key, data, id),
92            Self::Aes128 { key, dict } | Self::Aes256 { key, dict } => {
93                let crypt_dict = match target {
94                    DecryptionTarget::String => dict.string_filter,
95                    DecryptionTarget::Stream => dict.stream_filter,
96                };
97
98                match crypt_dict.cfm {
99                    DecryptorTag::None => Some(data.to_vec()),
100                    DecryptorTag::Rc4 => decrypt_rc4(key, data, id),
101                    DecryptorTag::Aes128 => decrypt_aes128(key, data, id),
102                    DecryptorTag::Aes256 => decrypt_aes256(key, data),
103                }
104            }
105        }
106    }
107}
108
109pub(crate) fn get(
110    dict: &Dict<'_>,
111    id: &[u8],
112    password: &[u8],
113) -> Result<Decryptor, DecryptionError> {
114    let filter = dict.get::<Name>(FILTER).ok_or(InvalidEncryption)?;
115
116    if filter.deref() != b"Standard" {
117        return Err(DecryptionError::UnsupportedAlgorithm);
118    }
119
120    let encryption_v = dict.get::<u8>(V).ok_or(InvalidEncryption)?;
121    let encrypt_metadata = dict.get::<bool>(ENCRYPT_META_DATA).unwrap_or(true);
122    let revision = dict.get::<u8>(R).ok_or(InvalidEncryption)?;
123    let length = match encryption_v {
124        1 => 40,
125        2 => dict.get::<u16>(LENGTH).unwrap_or(40),
126        4 => dict.get::<u16>(LENGTH).unwrap_or(128),
127        5 => 256,
128        _ => return Err(DecryptionError::UnsupportedAlgorithm),
129    };
130
131    let (algorithm, data) = match encryption_v {
132        1 => (DecryptorTag::Rc4, None),
133        2 => (DecryptorTag::Rc4, None),
134        4 => (
135            DecryptorTag::Aes128,
136            Some(DecryptorData::from_dict(dict, length).ok_or(InvalidEncryption)?),
137        ),
138        5 | 6 => (
139            DecryptorTag::Aes256,
140            Some(DecryptorData::from_dict(dict, length).ok_or(InvalidEncryption)?),
141        ),
142        _ => {
143            return Err(DecryptionError::UnsupportedAlgorithm);
144        }
145    };
146
147    let byte_length = length / 8;
148    // `/Length` is in bits and comes from the file, so anything below 8 divides
149    // down to a key of zero bytes -- which reaches `Rc4::new` and does
150    // `key[i % key.len()]`. Ported from hayro upstream (LaurenzV/hayro#1193);
151    // the clamp at the upper end already existed in decryption_key_rev1234 and
152    // says so in its comment, but nothing held the lower end.
153    if byte_length == 0 {
154        return Err(InvalidEncryption);
155    }
156
157    let owner_string = dict.get::<object::String>(O).ok_or(InvalidEncryption)?;
158    let user_string = dict.get::<object::String>(U).ok_or(InvalidEncryption)?;
159    let permissions = {
160        let raw = dict.get::<i64>(P).ok_or(InvalidEncryption)?;
161
162        if raw < 0 {
163            u32::from_be_bytes((raw as i32).to_be_bytes())
164        } else {
165            raw as u32
166        }
167    };
168
169    let mut decryption_key = if revision <= 4 {
170        let key = decryption_key_rev1234(
171            password,
172            encrypt_metadata,
173            revision,
174            byte_length,
175            &owner_string,
176            permissions,
177            id,
178        )?;
179        authenticate_user_password_rev234(revision, &key, id, &user_string)?;
180
181        key
182    } else {
183        decryption_key_rev56(dict, revision, password, &owner_string, &user_string)?
184    };
185
186    // See pdf.js issue 19484.
187    if encryption_v == 4 && decryption_key.len() < 16 {
188        decryption_key.resize(16, 0);
189    }
190
191    match algorithm {
192        DecryptorTag::None => Ok(Decryptor::None),
193        DecryptorTag::Rc4 => Ok(Decryptor::Rc4 {
194            key: decryption_key,
195        }),
196        DecryptorTag::Aes128 => Ok(Decryptor::Aes128 {
197            key: decryption_key,
198            dict: data.ok_or(InvalidEncryption)?,
199        }),
200        DecryptorTag::Aes256 => Ok(Decryptor::Aes256 {
201            key: decryption_key,
202            dict: data.ok_or(InvalidEncryption)?,
203        }),
204    }
205}
206
207/// Algorithm 1.A: Encryption of data using the AES algorithms
208fn decrypt_aes256(key: &[u8], data: &[u8]) -> Option<Vec<u8>> {
209    // a) Use the 32-byte file encryption key for the AES-256 symmetric key algorithm,
210    // along with the string or stream data to be encrypted.
211    // Use the AES algorithm in Cipher Block Chaining (CBC) mode, which requires an initialization
212    // vector. The block size parameter is set to 16 bytes, and the initialization
213    // vector is a 16-byte random number that is stored as the first 16 bytes of the
214    // encrypted stream or string.
215    let (iv, data) = data.split_at_checked(16)?;
216    let iv: [u8; 16] = iv.try_into().ok()?;
217    let cipher = AES256Cipher::new(key)?;
218    Some(cipher.decrypt_cbc(data, &iv, true))
219}
220
221fn decrypt_aes128(key: &[u8], data: &[u8], id: ObjectIdentifier) -> Option<Vec<u8>> {
222    decrypt_rc_aes(key, id, true, |key| {
223        // If using the AES algorithm, the Cipher Block Chaining (CBC) mode, which requires an initialization
224        // vector, is used. The block size parameter is set to 16 bytes, and the initialization vector is a 16-byte
225        // random number that is stored as the first 16 bytes of the encrypted stream or string.
226        let cipher = AES128Cipher::new(key)?;
227        let (iv, data) = data.split_at_checked(16)?;
228        let iv: [u8; 16] = iv.try_into().ok()?;
229
230        Some(cipher.decrypt_cbc(data, &iv, true))
231    })
232}
233
234fn decrypt_rc4(key: &[u8], data: &[u8], id: ObjectIdentifier) -> Option<Vec<u8>> {
235    decrypt_rc_aes(key, id, false, |key| {
236        let mut rc = Rc4::new(key);
237        Some(rc.decrypt(data))
238    })
239}
240
241/// Algorithm 1: Encryption of data using the RC4 or AES algorithms
242fn decrypt_rc_aes(
243    key: &[u8],
244    id: ObjectIdentifier,
245    aes: bool,
246    with_key: impl FnOnce(&[u8]) -> Option<Vec<u8>>,
247) -> Option<Vec<u8>> {
248    let n = key.len();
249    // a) Obtain the object number and generation number from the object identifier of
250    // the string or stream to be encrypted (see 7.3.10, "Indirect objects"). If the
251    // string is a direct object, use the identifier of the indirect object containing
252    // it.
253    let mut key = key.to_vec();
254
255    // b) For all strings and streams without crypt filter specifier; treating the
256    // object number and generation number as binary integers, extend the original
257    // n-byte file encryption key to n + 5 bytes by appending the low-order 3 bytes of
258    // the object number and the low-order 2 bytes of the generation number in that
259    // order, low-order byte first.
260    key.extend(&id.obj_number.to_le_bytes()[..3]);
261    key.extend(&id.gen_number.to_le_bytes()[..2]);
262
263    // If using the AES algorithm, extend the file encryption key an additional 4 bytes by adding the value
264    // "sAlT", which corresponds to the hexadecimal values 0x73, 0x41, 0x6C, 0x54. (This addition is done
265    // for backward compatibility and is not intended to provide additional security.)
266    if aes {
267        key.extend(b"sAlT");
268    }
269
270    // c) Initialise the MD5 hash function and pass the result of step (b) as input
271    // to this function.
272    let hash = md5::calculate(&key);
273
274    // d) Use the first (n + 5) bytes, up to a maximum of 16, of the output
275    // from the MD5 hash as the key for the RC4 or AES symmetric key algorithms,
276    // along with the string or stream data to be encrypted.
277    let final_key = &hash[..cmp::min(16, n + 5)];
278
279    with_key(final_key)
280}
281
282#[derive(Debug, Copy, Clone)]
283pub(crate) struct DecryptorData {
284    stream_filter: CryptDictionary,
285    string_filter: CryptDictionary,
286}
287
288impl DecryptorData {
289    fn from_dict(dict: &Dict<'_>, default_length: u16) -> Option<Self> {
290        let mut mappings = HashMap::new();
291
292        if let Some(dict) = dict.get::<Dict<'_>>(CF) {
293            for key in dict.keys() {
294                if let Some(dict) = dict.get::<Dict<'_>>(key.clone())
295                    && let Some(crypt_dict) = CryptDictionary::from_dict(&dict, default_length)
296                {
297                    mappings.insert(key.as_str().to_string(), crypt_dict);
298                }
299            }
300        }
301
302        let stm_f = *mappings
303            .get(dict.get::<Name>(STM_F)?.as_str())
304            .unwrap_or(&CryptDictionary::identity(default_length));
305        let str_f = *mappings
306            .get(dict.get::<Name>(STR_F)?.as_str())
307            .unwrap_or(&CryptDictionary::identity(default_length));
308
309        Some(Self {
310            stream_filter: stm_f,
311            string_filter: str_f,
312        })
313    }
314}
315
316#[derive(Debug, Copy, Clone)]
317struct CryptDictionary {
318    cfm: DecryptorTag,
319    _length: u16,
320}
321
322impl CryptDictionary {
323    fn from_dict(dict: &Dict<'_>, default_length: u16) -> Option<Self> {
324        let cfm = DecryptorTag::from_name(&dict.get::<Name>(CFM)?)?;
325        // The standard security handler expresses the Length entry in bytes (e.g., 32 means a
326        // length of 256 bits) and public-key security handlers express it as is (e.g., 256 means a
327        // length of 256 bits).
328        // Note: We only support the standard security handler.
329        let mut length = dict.get::<u16>(LENGTH).unwrap_or(default_length / 8);
330
331        // When CFM is AESV2, the Length key shall have the value of 128. When
332        // CFM is AESV3, the Length key shall have a value of 256.
333        if cfm == DecryptorTag::Aes128 {
334            length = 16;
335        } else if cfm == DecryptorTag::Aes256 {
336            length = 32;
337        }
338
339        Some(Self {
340            cfm,
341            _length: length,
342        })
343    }
344
345    fn identity(default_length: u16) -> Self {
346        Self {
347            cfm: DecryptorTag::None,
348            _length: default_length,
349        }
350    }
351}
352
353/// Algorithm 2.B: Computing a hash (revision 6 and later)
354fn compute_hash_rev56(
355    password: &[u8],
356    validation_salt: &[u8],
357    user_string: Option<&[u8]>,
358    revision: u8,
359) -> Result<[u8; 32], DecryptionError> {
360    // Take the SHA-256 hash of the original input to the algorithm and name the resulting
361    // 32 bytes, K.
362    let mut k = {
363        let mut input = Vec::new();
364        input.extend_from_slice(password);
365        input.extend_from_slice(validation_salt);
366
367        if let Some(user_string) = user_string {
368            input.extend_from_slice(user_string);
369        }
370
371        let hash = sha256::calculate(&input);
372
373        // Apparently revision 5 only uses this hash.
374        if revision == 5 {
375            return Ok(hash);
376        }
377
378        hash.to_vec()
379    };
380
381    let mut round: u16 = 0;
382
383    // Perform the following steps (a)-(d) 64 times:
384    loop {
385        // a) Make a new string, K1, consisting of 64 repetitions of the sequence:
386        // input password, K, the 48-byte user key. The 48 byte user key is only used when
387        // checking the owner password or creating the owner key. If checking the user
388        // password or creating the user key, K1 is the concatenation of the input
389        // password and K.
390        let k1 = {
391            let mut single: Vec<u8> = vec![];
392            single.extend(password);
393            single.extend(&k);
394
395            if let Some(user_string) = user_string {
396                single.extend(user_string);
397            }
398
399            single.repeat(64)
400        };
401
402        // b) Encrypt K1 with the AES-128 (CBC, no padding) algorithm,
403        // using the first 16 bytes of K as the key and the second 16 bytes of K as the
404        // initialization vector. The result of this encryption is E.
405        let e = {
406            let aes = AES128Cipher::new(&k[..16]).ok_or(InvalidEncryption)?;
407            let mut res = aes.encrypt_cbc(&k1, &k[16..32].try_into().expect("k is ≥32 bytes"));
408
409            // Remove padding that was added by `encrypt_cbc`.
410            res.truncate(k1.len());
411
412            res
413        };
414
415        // c) Taking the first 16 bytes of E as an unsigned big-endian integer,
416        // compute the remainder, modulo 3. If the result is 0, the next hash used is
417        // SHA-256, if the result is 1, the next hash used is SHA-384, if the result is
418        // 2, the next hash used is SHA-512.
419        let num = u128::from_be_bytes(e[..16].try_into().expect("AES output is ≥16 bytes")) % 3;
420
421        // d) Using the hash algorithm determined in step c, take the hash of E.
422        // The result is a new value of K, which will be 32, 48, or 64 bytes in length.
423        k = match num {
424            0 => sha256::calculate(&e).to_vec(),
425            1 => sha384::calculate(&e).to_vec(),
426            2 => sha512::calculate(&e).to_vec(),
427            _ => unreachable!(),
428        };
429
430        round += 1;
431
432        // Repeat the process (a-d) with this new value for K. Following 64 rounds
433        // (round number 0 to round number 63), do the following, starting with round
434        // number 64:
435        if round > 63 {
436            // e) Look at the very last byte of E. If the value of that byte
437            // (taken as an unsigned integer) is greater than the round number - 32,
438            // repeat steps (a-d) again.
439            let last_byte = *e.last().expect("AES output is non-empty");
440
441            // f) Repeat from steps (a-e) until the value of the last byte
442            // is < (round number) - 32.
443            // For some reason we need to use <= here?
444            if (last_byte as u16) <= round - 32 {
445                break;
446            }
447        }
448    }
449
450    // The first 32 bytes of the final K are the output of the algorithm.
451    let mut result = [0_u8; 32];
452    result.copy_from_slice(&k[..32]);
453    Ok(result)
454}
455
456/// Algorithm 2: Computing a file encryption key in order to encrypt a document (revision 4 and earlier)
457fn decryption_key_rev1234(
458    password: &[u8],
459    encrypt_metadata: bool,
460    revision: u8,
461    byte_length: u16,
462    owner_string: &object::String,
463    permissions: u32,
464    id: &[u8],
465) -> Result<Vec<u8>, DecryptionError> {
466    // PDF spec allows key lengths of 40–128 bits (5–16 bytes). Clamp to MD5
467    // output size (16) to prevent out-of-bounds access on corrupt Length values.
468    let byte_length = byte_length.min(16);
469
470    let mut md5_input = vec![];
471
472    // TODO: Convert to PDFDocEncoding.
473    // a) Pad or truncate password to 32 bytes using PASSWORD_PADDING.
474    let mut padded_password = [0_u8; 32];
475    let copy_len = password.len().min(32);
476    padded_password[..copy_len].copy_from_slice(&password[..copy_len]);
477    if copy_len < 32 {
478        padded_password[copy_len..].copy_from_slice(&PASSWORD_PADDING[..(32 - copy_len)]);
479    }
480
481    // b) Initialise the MD5 hash function and pass the
482    // result of step a) as input to this function.
483    md5_input.extend(&padded_password);
484
485    // c) Pass the value of the encryption dictionary's O entry
486    // to the MD5 hash function.
487    md5_input.extend(owner_string.as_ref());
488
489    // d) Convert the integer value of the P entry to a 32-bit unsigned
490    // binary number and pass these bytes to the MD5 hash function, low-order byte first.
491    md5_input.extend(permissions.to_le_bytes());
492
493    // e) Pass the first element of the file's file identifier array to the MD5 hash function.
494    md5_input.extend(id);
495
496    // f) (Security handlers of revision 4 or greater) If document metadata
497    // is not being encrypted, pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
498    if !encrypt_metadata && revision >= 4 {
499        md5_input.extend(&[0xff, 0xff, 0xff, 0xff]);
500    }
501
502    // g) Finish the hash.
503    let mut hash = md5::calculate(&md5_input);
504
505    // h) For revisions >= 3, do the following 50 times: Take the output from the previous
506    // MD5 hash and pass the first n bytes of the output as input into a new MD5 hash,
507    // where n is the number of bytes of the file encryption key as defined by the value
508    // of the encryption dictionary's `Length` entry.
509    if revision >= 3 {
510        for _ in 0..50 {
511            hash = md5::calculate(&hash[..byte_length as usize]);
512        }
513    }
514
515    let decryption_key = hash[..byte_length as usize].to_vec();
516    Ok(decryption_key)
517}
518
519/// Algorithm 6: Authenticating the user password
520fn authenticate_user_password_rev234(
521    revision: u8,
522    decryption_key: &[u8],
523    id: &[u8],
524    user_string: &object::String,
525) -> Result<(), DecryptionError> {
526    // a) Perform all but the last step of Algorithm 4 (revision 2) or Algorithm 5 (revision 3 + 4).
527    let result = match revision {
528        2 => user_password_rev2(decryption_key),
529        3 | 4 => user_password_rev34(decryption_key, id),
530        _ => return Err(InvalidEncryption),
531    };
532
533    // b) If the result of step (a) is equal to the value of the encryption dictionary's
534    // U entry (comparing on the first 16 bytes in the case of security handlers of
535    // revision 3 or greater), the password supplied is the correct user password.
536    match revision {
537        2 => {
538            if result.as_slice() != user_string.as_ref() {
539                return Err(DecryptionError::PasswordProtected);
540            }
541        }
542        3 | 4 => {
543            if Some(&result[..16]) != user_string.as_ref().get(0..16) {
544                return Err(DecryptionError::PasswordProtected);
545            }
546        }
547        _ => unreachable!(),
548    }
549
550    Ok(())
551}
552
553/// Algorithm 4: Computing the encryption dictionary’s U-entry value
554/// (Security handlers of revision 2).
555fn user_password_rev2(decryption_key: &[u8]) -> Vec<u8> {
556    // a) Create a file encryption key based on the user password string.
557    // b) Encrypt the 32-byte padding string using an RC4 encryption
558    // function with the file encryption key from the preceding step.
559    let mut rc = Rc4::new(decryption_key);
560    rc.decrypt(&PASSWORD_PADDING)
561}
562
563/// Algorithm 5: Computing the encryption dictionary’s U (user password)
564/// value (Security handlers of revision 3 or 4).
565fn user_password_rev34(decryption_key: &[u8], id: &[u8]) -> Vec<u8> {
566    // a) Create a file encryption key based on the user password string.
567    let mut rc = Rc4::new(decryption_key);
568
569    let mut input = vec![];
570    // b) Initialise the MD5 hash function and pass the 32-byte padding string.
571    input.extend(PASSWORD_PADDING);
572
573    // c) Pass the first element of the file's file identifier array to the hash function
574    // and finish the hash.
575    input.extend(id);
576    let hash = md5::calculate(&input);
577
578    // d) Encrypt the 16-byte result of the hash, using an RC4 encryption function with
579    // the encryption key from step (a).
580    let mut encrypted = rc.encrypt(&hash);
581
582    // e) Do the following 19 times: Take the output from the previous invocation of the
583    // RC4 function and pass it as input to a new invocation of the function; use a file
584    // encryption key generated by taking each byte of the original file encryption key
585    // obtained in step (a) and performing an XOR (exclusive or) operation between that
586    // byte and the single-byte value of the iteration counter (from 1 to 19).
587    for i in 1..=19 {
588        let mut key = decryption_key.to_vec();
589        for byte in &mut key {
590            *byte ^= i;
591        }
592
593        let mut rc = Rc4::new(&key);
594        encrypted = rc.encrypt(&encrypted);
595    }
596
597    encrypted.resize(32, 0);
598    encrypted
599}
600
601/// Algorithm 2.A: Retrieving the file encryption key from an encrypted document in order to decrypt it (revision 6 and later)
602fn decryption_key_rev56(
603    dict: &Dict<'_>,
604    revision: u8,
605    password: &[u8],
606    owner_string: &object::String,
607    user_string: &object::String,
608) -> Result<Vec<u8>, DecryptionError> {
609    // a) The UTF-8 password string shall be generated from Unicode input by processing the input string with
610    // the SASLprep (Internet RFC 4013) profile of stringprep (Internet RFC 3454) using the Normalize and BiDi
611    // options, and then converting to a UTF-8 representation.
612    // TODO: Do the above.
613    // b) Truncate the UTF-8 representation to 127 bytes if it is longer than 127 bytes.
614    let password = &password[..password.len().min(127)];
615
616    let string_len = if revision <= 4 { 32 } else { 48 };
617
618    let trimmed_os = owner_string.get(..string_len).ok_or(InvalidEncryption)?;
619
620    let (owner_hash, owner_tail) = trimmed_os.split_at_checked(32).ok_or(InvalidEncryption)?;
621    let (owner_validation_salt, owner_key_salt) =
622        owner_tail.split_at_checked(8).ok_or(InvalidEncryption)?;
623
624    let trimmed_us = user_string.get(..string_len).ok_or(InvalidEncryption)?;
625    let (user_hash, user_tail) = trimmed_us.split_at_checked(32).ok_or(InvalidEncryption)?;
626    let (user_validation_salt, user_key_salt) =
627        user_tail.split_at_checked(8).ok_or(InvalidEncryption)?;
628
629    // c) Test the password against the owner key by computing a hash using algorithm 2.B
630    // with an input string consisting of the UTF-8 password concatenated with the 8 bytes of
631    // owner Validation Salt, concatenated with the 48-byte U string. If the 32-byte result
632    // matches the first 32 bytes of the O string, this is the owner password.
633    if compute_hash_rev56(password, owner_validation_salt, Some(trimmed_us), revision)?
634        == owner_hash
635    {
636        // d) Compute an intermediate owner key by computing a hash using algorithm 2.B with an input string
637        // consisting of the UTF-8 owner password concatenated with the 8 bytes of owner Key Salt,
638        // concatenated with the 48-byte U string. The 32-byte result is the key used to decrypt the 32-byte OE string
639        // using AES-256 in CBC mode with no padding and an initialization vector of zero. The 32-byte result is the file encryption key.
640        let intermediate_owner_key =
641            compute_hash_rev56(password, owner_key_salt, Some(trimmed_us), revision)?;
642
643        let oe_string = dict.get::<object::String>(OE).ok_or(InvalidEncryption)?;
644
645        if oe_string.len() != 32 {
646            return Err(InvalidEncryption);
647        }
648
649        let cipher = AES256Cipher::new(&intermediate_owner_key).ok_or(InvalidEncryption)?;
650        let zero_iv = [0_u8; 16];
651
652        Ok(cipher.decrypt_cbc(&oe_string, &zero_iv, false))
653    } else if compute_hash_rev56(password, user_validation_salt, None, revision)? == user_hash {
654        // e) Compute an intermediate user key by computing a hash using algorithm 2.B with an input string
655        // consisting of the UTF-8 user password concatenated with the 8 bytes of user Key Salt. The 32-byte result
656        // is the key used to decrypt the 32-byte UE string using AES-256 in CBC mode with no padding and an
657        // initialization vector of zero. The 32-byte result is the file encryption key.
658        let intermediate_key = compute_hash_rev56(password, user_key_salt, None, revision)?;
659
660        let ue_string = dict.get::<object::String>(UE).ok_or(InvalidEncryption)?;
661
662        if ue_string.len() != 32 {
663            return Err(InvalidEncryption);
664        }
665
666        let cipher = AES256Cipher::new(&intermediate_key).ok_or(InvalidEncryption)?;
667        let zero_iv = [0_u8; 16];
668
669        Ok(cipher.decrypt_cbc(&ue_string, &zero_iv, false))
670    } else {
671        Err(DecryptionError::PasswordProtected)
672    }
673
674    // TODO:
675    // f) Decrypt the 16-byte Perms string using AES-256 in ECB mode with an initialization vector of zero and
676    // the file encryption key as the key. Verify that bytes 9-11 of the result are the characters "a", "d",
677    // "b". Bytes 0-3 of the decrypted Perms entry, treated as a little-endian integer, are the user
678    // permissions. They shall match the value in the P key.
679}
680
681#[cfg(test)]
682mod upstream_hardening_tests {
683    use super::*;
684    use crate::reader::{Reader, ReaderContext, ReaderExt};
685
686    fn encryption_dict(data: &[u8]) -> Dict<'_> {
687        Reader::new(data)
688            .read_with_context::<Dict<'_>>(&ReaderContext::dummy())
689            .expect("the test's own dictionary must parse")
690    }
691
692    /// Ported from LaurenzV/hayro#1193.
693    ///
694    /// `/Length` is in bits and comes from the file. Anything below 8 divides
695    /// down to a key of zero bytes, and the key reaches `Rc4::new`, which does
696    /// `key[i % key.len()]` -- a remainder by zero. `byte_length.min(16)` a few
697    /// lines below clamps the upper end and says so in its comment; nothing
698    /// clamped the lower end.
699    ///
700    /// `/V 2` is what makes `/Length` reach the key derivation at all: V 1 hard-codes
701    /// 40 and V 5 hard-codes 256, so neither can express this.
702    #[test]
703    fn an_encryption_length_below_one_byte_is_rejected_not_divided_by() {
704        let dict = encryption_dict(
705            b"<< /Filter /Standard /V 2 /R 2 /Length 4 \
706               /O <0000000000000000000000000000000000000000000000000000000000000000> \
707               /U <0000000000000000000000000000000000000000000000000000000000000000> \
708               /P -1 >>",
709        );
710
711        assert!(matches!(
712            get(&dict, b"", b""),
713            Err(DecryptionError::InvalidEncryption)
714        ));
715    }
716
717    /// The bound must reject only what it is for. `/Length 40` is the PDF
718    /// default and derives a five-byte key, so a guard that rejected it -- or
719    /// one written as `<= 8` bits rather than `< 8` -- would fail here.
720    #[test]
721    fn the_ordinary_forty_bit_length_still_derives_a_key() {
722        let dict = encryption_dict(
723            b"<< /Filter /Standard /V 2 /R 2 /Length 40 \
724               /O <0000000000000000000000000000000000000000000000000000000000000000> \
725               /U <0000000000000000000000000000000000000000000000000000000000000000> \
726               /P -1 >>",
727        );
728
729        // The password is wrong, so authentication fails -- but it fails as a
730        // password problem, having derived a key, rather than as InvalidEncryption.
731        assert!(!matches!(
732            get(&dict, b"", b""),
733            Err(DecryptionError::InvalidEncryption)
734        ));
735    }
736}