Skip to main content

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