1use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit, generic_array::GenericArray};
22use aes::{Aes128, Aes256};
23use md5::{Digest, Md5};
24use sha2::{Sha256, Sha384, Sha512};
25
26use crate::error::{PdfError, PdfResult};
27use crate::types::{ObjectRef, PdfDictionary, PdfValue};
28
29const 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum SecurityRevision {
37 R2,
38 R3,
39 R4,
40 R5,
41 R6,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum CryptMethod {
53 Identity,
54 V2,
55 AesV2,
56 AesV3,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum BytesKind {
63 String,
64 Stream,
65}
66
67#[derive(Debug, Clone)]
68pub struct StandardSecurityHandler {
69 file_key: Vec<u8>,
70 string_method: CryptMethod,
71 stream_method: CryptMethod,
72 encrypt_metadata: bool,
77}
78
79impl StandardSecurityHandler {
80 pub fn open(
84 encrypt_dict: &PdfDictionary,
85 id_first: &[u8],
86 password: &[u8],
87 ) -> PdfResult<Option<Self>> {
88 let filter = encrypt_dict
89 .get("Filter")
90 .and_then(PdfValue::as_name)
91 .unwrap_or("");
92 if filter != "Standard" {
93 return Err(PdfError::Unsupported(format!(
94 "encryption filter /{filter} is not supported"
95 )));
96 }
97 let v = encrypt_dict
98 .get("V")
99 .and_then(PdfValue::as_integer)
100 .unwrap_or(0);
101 let r = encrypt_dict
102 .get("R")
103 .and_then(PdfValue::as_integer)
104 .unwrap_or(0);
105 let revision = match r {
106 2 => SecurityRevision::R2,
107 3 => SecurityRevision::R3,
108 4 => SecurityRevision::R4,
109 5 => SecurityRevision::R5,
110 6 => SecurityRevision::R6,
111 other => {
112 return Err(PdfError::Unsupported(format!(
113 "Standard security handler revision {other} is not supported (only R=2..R=6 handled)"
114 )));
115 }
116 };
117
118 if v == 5 {
121 return open_v5(encrypt_dict, revision, password);
122 }
123
124 let (string_method, stream_method, key_length_bytes) = match v {
125 1 | 2 => {
126 let bits = encrypt_dict
127 .get("Length")
128 .and_then(PdfValue::as_integer)
129 .unwrap_or(40);
130 if bits % 8 != 0 || !(40..=128).contains(&bits) {
131 return Err(PdfError::Corrupt(format!(
132 "invalid /Length {bits} in Encrypt dictionary"
133 )));
134 }
135 (CryptMethod::V2, CryptMethod::V2, (bits / 8) as usize)
136 }
137 4 => {
138 let (strf, stmf) = resolve_v4_crypt_filters(encrypt_dict)?;
141 (strf, stmf, 16)
142 }
143 other => {
144 return Err(PdfError::Unsupported(format!(
145 "Standard security handler V={other} is not supported (only V=1, V=2, V=4, and V=5 handled)"
146 )));
147 }
148 };
149
150 let encrypt_metadata = if matches!(revision, SecurityRevision::R4) {
153 encrypt_dict
154 .get("EncryptMetadata")
155 .and_then(PdfValue::as_bool)
156 .unwrap_or(true)
157 } else {
158 true
159 };
160
161 let o = pdf_string_bytes(encrypt_dict, "O")?;
162 let u = pdf_string_bytes(encrypt_dict, "U")?;
163 let p = encrypt_dict
164 .get("P")
165 .and_then(PdfValue::as_integer)
166 .ok_or_else(|| PdfError::Corrupt("Encrypt dictionary missing /P".to_string()))?;
167 if o.len() != 32 || u.len() != 32 {
168 return Err(PdfError::Corrupt(
169 "Encrypt /O and /U must each be 32 bytes".to_string(),
170 ));
171 }
172
173 let user_file_key = compute_file_key(
175 password,
176 &o,
177 p as i32,
178 id_first,
179 key_length_bytes,
180 revision,
181 encrypt_metadata,
182 );
183 if authenticate_user_password(&user_file_key, revision, &u, id_first) {
184 return Ok(Some(Self {
185 file_key: user_file_key,
186 string_method,
187 stream_method,
188 encrypt_metadata,
189 }));
190 }
191
192 let recovered_user_password =
198 recover_user_password_from_owner(password, &o, revision, key_length_bytes);
199 let owner_file_key = compute_file_key(
200 &recovered_user_password,
201 &o,
202 p as i32,
203 id_first,
204 key_length_bytes,
205 revision,
206 encrypt_metadata,
207 );
208 if authenticate_user_password(&owner_file_key, revision, &u, id_first) {
209 return Ok(Some(Self {
210 file_key: owner_file_key,
211 string_method,
212 stream_method,
213 encrypt_metadata,
214 }));
215 }
216
217 Ok(None)
218 }
219
220 pub fn from_file_key(
226 file_key: Vec<u8>,
227 string_method: CryptMethod,
228 stream_method: CryptMethod,
229 encrypt_metadata: bool,
230 ) -> Self {
231 Self {
232 file_key,
233 string_method,
234 stream_method,
235 encrypt_metadata,
236 }
237 }
238
239 pub fn encrypts_metadata(&self) -> bool {
243 self.encrypt_metadata
244 }
245
246 pub fn decrypt_bytes(
252 &self,
253 bytes: &[u8],
254 object_ref: ObjectRef,
255 kind: BytesKind,
256 ) -> PdfResult<Vec<u8>> {
257 let method = match kind {
258 BytesKind::String => self.string_method,
259 BytesKind::Stream => self.stream_method,
260 };
261 match method {
262 CryptMethod::Identity => Ok(bytes.to_vec()),
263 CryptMethod::V2 => Ok(rc4(&self.object_key(object_ref, method), bytes)),
264 CryptMethod::AesV2 => aes_128_cbc_decrypt(&self.object_key(object_ref, method), bytes),
265 CryptMethod::AesV3 => {
266 aes_256_cbc_decrypt(&self.file_key, bytes)
270 }
271 }
272 }
273
274 fn object_key(&self, object_ref: ObjectRef, method: CryptMethod) -> Vec<u8> {
275 let suffix_len = if matches!(method, CryptMethod::AesV2) {
279 9
280 } else {
281 5
282 };
283 let mut material = Vec::with_capacity(self.file_key.len() + suffix_len);
284 material.extend_from_slice(&self.file_key);
285 let num = object_ref.object_number.to_le_bytes();
286 material.push(num[0]);
287 material.push(num[1]);
288 material.push(num[2]);
289 let generation = object_ref.generation.to_le_bytes();
290 material.push(generation[0]);
291 material.push(generation[1]);
292 if matches!(method, CryptMethod::AesV2) {
293 material.extend_from_slice(b"sAlT");
294 }
295 let digest = md5_bytes(&material);
296 let truncated_len = (self.file_key.len() + 5).min(16);
297 digest[..truncated_len].to_vec()
298 }
299}
300
301fn open_v5(
302 encrypt_dict: &PdfDictionary,
303 revision: SecurityRevision,
304 password: &[u8],
305) -> PdfResult<Option<StandardSecurityHandler>> {
306 if !matches!(revision, SecurityRevision::R5 | SecurityRevision::R6) {
307 return Err(PdfError::Unsupported(format!(
308 "V=5 Encrypt dictionary requires R=5 or R=6, got {revision:?}"
309 )));
310 }
311 let (strf, stmf) = resolve_v5_crypt_filters(encrypt_dict)?;
312
313 let encrypt_metadata = encrypt_dict
314 .get("EncryptMetadata")
315 .and_then(PdfValue::as_bool)
316 .unwrap_or(true);
317
318 let o = pdf_string_bytes(encrypt_dict, "O")?;
319 let u = pdf_string_bytes(encrypt_dict, "U")?;
320 let oe = pdf_string_bytes(encrypt_dict, "OE")?;
321 let ue = pdf_string_bytes(encrypt_dict, "UE")?;
322 if o.len() != 48 || u.len() != 48 {
323 return Err(PdfError::Corrupt(
324 "V=5 Encrypt /O and /U must each be 48 bytes".to_string(),
325 ));
326 }
327 if oe.len() != 32 || ue.len() != 32 {
328 return Err(PdfError::Corrupt(
329 "V=5 Encrypt /OE and /UE must each be 32 bytes".to_string(),
330 ));
331 }
332
333 let truncated_password = &password[..password.len().min(127)];
335
336 let u_validation_salt = &u[32..40];
339 let u_key_salt = &u[40..48];
340 let user_hash = pdf_2_b_hash(truncated_password, u_validation_salt, None, revision);
341 if user_hash[..32] == u[..32] {
342 let intermediate_key = pdf_2_b_hash(truncated_password, u_key_salt, None, revision);
343 let file_key = aes_256_cbc_decrypt_no_pad(&intermediate_key[..32], &[0u8; 16], &ue)?;
344 return Ok(Some(StandardSecurityHandler {
345 file_key,
346 string_method: strf,
347 stream_method: stmf,
348 encrypt_metadata,
349 }));
350 }
351
352 let o_validation_salt = &o[32..40];
356 let o_key_salt = &o[40..48];
357 let owner_hash = pdf_2_b_hash(
358 truncated_password,
359 o_validation_salt,
360 Some(&u[..48]),
361 revision,
362 );
363 if owner_hash[..32] == o[..32] {
364 let intermediate_key =
365 pdf_2_b_hash(truncated_password, o_key_salt, Some(&u[..48]), revision);
366 let file_key = aes_256_cbc_decrypt_no_pad(&intermediate_key[..32], &[0u8; 16], &oe)?;
367 return Ok(Some(StandardSecurityHandler {
368 file_key,
369 string_method: strf,
370 stream_method: stmf,
371 encrypt_metadata,
372 }));
373 }
374
375 Ok(None)
376}
377
378fn pdf_2_b_hash(
382 password: &[u8],
383 salt: &[u8],
384 user_vector: Option<&[u8]>,
385 revision: SecurityRevision,
386) -> Vec<u8> {
387 let mut hasher = Sha256::new();
388 hasher.update(password);
389 hasher.update(salt);
390 if let Some(vector) = user_vector {
391 hasher.update(vector);
392 }
393 let mut k: Vec<u8> = hasher.finalize().to_vec();
394
395 if matches!(revision, SecurityRevision::R5) {
396 return k;
397 }
398
399 let user_vector = user_vector.unwrap_or(&[]);
401 let mut round: u32 = 0;
402 loop {
403 let mut k1 = Vec::with_capacity((password.len() + k.len() + user_vector.len()) * 64);
405 for _ in 0..64 {
406 k1.extend_from_slice(password);
407 k1.extend_from_slice(&k);
408 k1.extend_from_slice(user_vector);
409 }
410
411 let key: [u8; 16] = k[..16].try_into().expect("K is at least 32 bytes");
417 let iv: [u8; 16] = k[16..32].try_into().expect("K is at least 32 bytes");
418 let encrypted = aes_128_cbc_encrypt_no_pad(&key, &iv, &k1);
419
420 let selector: u32 = encrypted[..16]
422 .iter()
423 .map(|byte| u32::from(*byte % 3))
424 .sum::<u32>()
425 % 3;
426 k = match selector {
427 0 => Sha256::digest(&encrypted).to_vec(),
428 1 => Sha384::digest(&encrypted).to_vec(),
429 _ => Sha512::digest(&encrypted).to_vec(),
430 };
431
432 let last_byte = *encrypted.last().expect("AES output is non-empty");
433 round += 1;
434 if round >= 64 && u32::from(last_byte) <= round.saturating_sub(32) {
435 break;
436 }
437 }
438
439 k.truncate(32);
440 k
441}
442
443fn resolve_v5_crypt_filters(encrypt_dict: &PdfDictionary) -> PdfResult<(CryptMethod, CryptMethod)> {
444 let strf = encrypt_dict
445 .get("StrF")
446 .and_then(PdfValue::as_name)
447 .unwrap_or("Identity");
448 let stmf = encrypt_dict
449 .get("StmF")
450 .and_then(PdfValue::as_name)
451 .unwrap_or("Identity");
452 let cf = encrypt_dict.get("CF").and_then(|value| match value {
453 PdfValue::Dictionary(dict) => Some(dict),
454 _ => None,
455 });
456 Ok((
457 resolve_crypt_filter_method(cf, strf)?,
458 resolve_crypt_filter_method(cf, stmf)?,
459 ))
460}
461
462pub(crate) fn resolve_v4_crypt_filters(
463 encrypt_dict: &PdfDictionary,
464) -> PdfResult<(CryptMethod, CryptMethod)> {
465 let strf = encrypt_dict
466 .get("StrF")
467 .and_then(PdfValue::as_name)
468 .unwrap_or("Identity");
469 let stmf = encrypt_dict
470 .get("StmF")
471 .and_then(PdfValue::as_name)
472 .unwrap_or("Identity");
473 let cf = encrypt_dict.get("CF").and_then(|value| match value {
474 PdfValue::Dictionary(dict) => Some(dict),
475 _ => None,
476 });
477 Ok((
478 resolve_crypt_filter_method(cf, strf)?,
479 resolve_crypt_filter_method(cf, stmf)?,
480 ))
481}
482
483fn resolve_crypt_filter_method(cf: Option<&PdfDictionary>, name: &str) -> PdfResult<CryptMethod> {
484 if name == "Identity" {
488 return Ok(CryptMethod::Identity);
489 }
490 let subfilter = cf
491 .and_then(|dict| dict.get(name))
492 .and_then(|value| match value {
493 PdfValue::Dictionary(dict) => Some(dict),
494 _ => None,
495 })
496 .ok_or_else(|| {
497 PdfError::Corrupt(format!(
498 "Encrypt /CF is missing the crypt filter entry /{name}"
499 ))
500 })?;
501 let cfm = subfilter
502 .get("CFM")
503 .and_then(PdfValue::as_name)
504 .ok_or_else(|| {
505 PdfError::Corrupt(format!("crypt filter /{name} is missing the /CFM entry"))
506 })?;
507 match cfm {
508 "V2" => Ok(CryptMethod::V2),
509 "AESV2" => Ok(CryptMethod::AesV2),
510 "AESV3" => Ok(CryptMethod::AesV3),
511 "None" => Ok(CryptMethod::Identity),
512 other => Err(PdfError::Unsupported(format!(
513 "crypt filter method /{other} is not supported (only /V2, /AESV2, and /AESV3 handled)"
514 ))),
515 }
516}
517
518fn aes_128_cbc_decrypt(key: &[u8], data: &[u8]) -> PdfResult<Vec<u8>> {
522 if key.len() != 16 {
523 return Err(PdfError::Corrupt(format!(
524 "AES-128 object key must be 16 bytes, got {}",
525 key.len()
526 )));
527 }
528 if data.len() < 32 || data.len() % 16 != 0 {
529 return Err(PdfError::Corrupt(format!(
530 "AES-128-CBC ciphertext must be at least 32 bytes and a multiple of 16; got {}",
531 data.len()
532 )));
533 }
534 let cipher = Aes128::new_from_slice(key)
535 .map_err(|error| PdfError::Corrupt(format!("AES-128 key rejected by cipher: {error}")))?;
536 let mut prev_block: [u8; 16] = data[..16].try_into().expect("slice is 16 bytes");
537 let mut output = Vec::with_capacity(data.len() - 16);
538 for chunk in data[16..].chunks(16) {
539 let mut block = GenericArray::clone_from_slice(chunk);
540 cipher.decrypt_block(&mut block);
541 for (plain_byte, iv_byte) in block.iter_mut().zip(prev_block.iter()) {
542 *plain_byte ^= iv_byte;
543 }
544 output.extend_from_slice(block.as_slice());
545 prev_block.copy_from_slice(chunk);
546 }
547 strip_pkcs7(output)
548}
549
550fn aes_256_cbc_decrypt(key: &[u8], data: &[u8]) -> PdfResult<Vec<u8>> {
554 if key.len() != 32 {
555 return Err(PdfError::Corrupt(format!(
556 "AES-256 file key must be 32 bytes, got {}",
557 key.len()
558 )));
559 }
560 if data.len() < 32 || data.len() % 16 != 0 {
561 return Err(PdfError::Corrupt(format!(
562 "AES-256-CBC ciphertext must be at least 32 bytes and a multiple of 16; got {}",
563 data.len()
564 )));
565 }
566 let cipher = Aes256::new_from_slice(key)
567 .map_err(|error| PdfError::Corrupt(format!("AES-256 key rejected by cipher: {error}")))?;
568 let mut prev_block: [u8; 16] = data[..16].try_into().expect("slice is 16 bytes");
569 let mut output = Vec::with_capacity(data.len() - 16);
570 for chunk in data[16..].chunks(16) {
571 let mut block = GenericArray::clone_from_slice(chunk);
572 cipher.decrypt_block(&mut block);
573 for (plain_byte, iv_byte) in block.iter_mut().zip(prev_block.iter()) {
574 *plain_byte ^= iv_byte;
575 }
576 output.extend_from_slice(block.as_slice());
577 prev_block.copy_from_slice(chunk);
578 }
579 strip_pkcs7(output)
580}
581
582fn aes_256_cbc_decrypt_no_pad(key: &[u8], iv: &[u8], data: &[u8]) -> PdfResult<Vec<u8>> {
587 if key.len() != 32 {
588 return Err(PdfError::Corrupt(format!(
589 "AES-256 key must be 32 bytes, got {}",
590 key.len()
591 )));
592 }
593 if iv.len() != 16 {
594 return Err(PdfError::Corrupt(format!(
595 "AES-256-CBC IV must be 16 bytes, got {}",
596 iv.len()
597 )));
598 }
599 if data.is_empty() || data.len() % 16 != 0 {
600 return Err(PdfError::Corrupt(format!(
601 "AES-256-CBC payload must be a non-empty multiple of 16 bytes; got {}",
602 data.len()
603 )));
604 }
605 let cipher = Aes256::new_from_slice(key)
606 .map_err(|error| PdfError::Corrupt(format!("AES-256 key rejected by cipher: {error}")))?;
607 let mut prev_block: [u8; 16] = iv.try_into().expect("iv length validated");
608 let mut output = Vec::with_capacity(data.len());
609 for chunk in data.chunks(16) {
610 let mut block = GenericArray::clone_from_slice(chunk);
611 cipher.decrypt_block(&mut block);
612 for (plain_byte, iv_byte) in block.iter_mut().zip(prev_block.iter()) {
613 *plain_byte ^= iv_byte;
614 }
615 output.extend_from_slice(block.as_slice());
616 prev_block.copy_from_slice(chunk);
617 }
618 Ok(output)
619}
620
621fn aes_128_cbc_encrypt_no_pad(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
626 assert!(
629 data.len() % 16 == 0,
630 "Algorithm 2.B K1 must be block-aligned, got {}",
631 data.len()
632 );
633 let cipher = Aes128::new_from_slice(key).expect("key length validated at compile time");
634 let mut output = Vec::with_capacity(data.len());
635 let mut prev: [u8; 16] = *iv;
636 for chunk in data.chunks(16) {
637 let mut buf = [0u8; 16];
638 for ((b, plain), iv_byte) in buf.iter_mut().zip(chunk.iter()).zip(prev.iter()) {
639 *b = plain ^ iv_byte;
640 }
641 let mut block = GenericArray::clone_from_slice(&buf);
642 cipher.encrypt_block(&mut block);
643 output.extend_from_slice(block.as_slice());
644 prev.copy_from_slice(block.as_slice());
645 }
646 output
647}
648
649fn strip_pkcs7(mut data: Vec<u8>) -> PdfResult<Vec<u8>> {
650 let Some(&pad) = data.last() else {
651 return Err(PdfError::Corrupt(
652 "AES-128-CBC plaintext is empty — missing PKCS#7 padding".to_string(),
653 ));
654 };
655 if pad == 0 || pad > 16 || (pad as usize) > data.len() {
656 return Err(PdfError::Corrupt(format!(
657 "AES-128-CBC PKCS#7 padding byte {pad} is out of range"
658 )));
659 }
660 let new_len = data.len() - pad as usize;
661 if !data[new_len..].iter().all(|byte| *byte == pad) {
662 return Err(PdfError::Corrupt(
663 "AES-128-CBC PKCS#7 padding bytes do not match".to_string(),
664 ));
665 }
666 data.truncate(new_len);
667 Ok(data)
668}
669
670fn pdf_string_bytes(dict: &PdfDictionary, key: &str) -> PdfResult<Vec<u8>> {
671 match dict.get(key) {
672 Some(PdfValue::String(s)) => Ok(s.0.clone()),
673 Some(_) => Err(PdfError::Corrupt(format!("Encrypt /{key} is not a string"))),
674 None => Err(PdfError::Corrupt(format!(
675 "Encrypt dictionary missing /{key}"
676 ))),
677 }
678}
679
680fn compute_file_key(
681 password: &[u8],
682 o_entry: &[u8],
683 permissions: i32,
684 id_first: &[u8],
685 key_length_bytes: usize,
686 revision: SecurityRevision,
687 encrypt_metadata: bool,
688) -> Vec<u8> {
689 let padded = pad_password(password);
692 let mut hasher = Md5::new();
693 hasher.update(padded);
694 hasher.update(o_entry);
696 hasher.update(permissions.to_le_bytes());
698 hasher.update(id_first);
700 if matches!(revision, SecurityRevision::R4) && !encrypt_metadata {
703 hasher.update([0xFFu8; 4]);
704 }
705 let mut digest = hasher.finalize_reset();
706
707 if matches!(revision, SecurityRevision::R3 | SecurityRevision::R4) {
709 for _ in 0..50 {
710 hasher.update(&digest[..key_length_bytes]);
711 digest = hasher.finalize_reset();
712 }
713 }
714 digest[..key_length_bytes].to_vec()
715}
716
717fn pad_password(password: &[u8]) -> [u8; 32] {
718 let mut out = [0u8; 32];
719 let take = password.len().min(32);
720 out[..take].copy_from_slice(&password[..take]);
721 if take < 32 {
722 out[take..].copy_from_slice(&PASSWORD_PADDING[..32 - take]);
723 }
724 out
725}
726
727fn recover_user_password_from_owner(
728 owner_password: &[u8],
729 o_entry: &[u8],
730 revision: SecurityRevision,
731 key_length_bytes: usize,
732) -> Vec<u8> {
733 let padded = pad_password(owner_password);
742 let mut hasher = Md5::new();
743 hasher.update(padded);
744 let mut digest = hasher.finalize_reset();
745 if matches!(revision, SecurityRevision::R3 | SecurityRevision::R4) {
746 for _ in 0..50 {
747 hasher.update(&digest[..key_length_bytes]);
748 digest = hasher.finalize_reset();
749 }
750 }
751 let base_key = digest[..key_length_bytes].to_vec();
752
753 match revision {
754 SecurityRevision::R2 => rc4(&base_key, o_entry),
755 SecurityRevision::R3 | SecurityRevision::R4 => {
756 let mut buffer = o_entry.to_vec();
757 for i in (0u8..=19).rev() {
758 let key: Vec<u8> = base_key.iter().map(|byte| byte ^ i).collect();
759 buffer = rc4(&key, &buffer);
760 }
761 buffer
762 }
763 SecurityRevision::R5 | SecurityRevision::R6 => {
764 unreachable!("V=5 takes open_v5; Algorithm 7 is not applicable to R=5 / R=6")
765 }
766 }
767}
768
769fn authenticate_user_password(
770 file_key: &[u8],
771 revision: SecurityRevision,
772 u_entry: &[u8],
773 id_first: &[u8],
774) -> bool {
775 match revision {
776 SecurityRevision::R2 => {
777 let encrypted = rc4(file_key, &PASSWORD_PADDING);
780 encrypted == u_entry
781 }
782 SecurityRevision::R5 | SecurityRevision::R6 => {
783 unreachable!("V=5 takes open_v5; Algorithm 5 is not applicable to R=5 / R=6")
784 }
785 SecurityRevision::R3 | SecurityRevision::R4 => {
786 let mut hasher = Md5::new();
788 hasher.update(PASSWORD_PADDING);
789 hasher.update(id_first);
790 let seed = hasher.finalize();
791 let mut buffer = rc4(file_key, &seed);
792 for i in 1u8..=19 {
793 let key: Vec<u8> = file_key.iter().map(|byte| byte ^ i).collect();
794 buffer = rc4(&key, &buffer);
795 }
796 buffer.as_slice() == &u_entry[..16]
799 }
800 }
801}
802
803fn md5_bytes(input: &[u8]) -> [u8; 16] {
804 let mut hasher = Md5::new();
805 hasher.update(input);
806 hasher.finalize().into()
807}
808
809fn rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
810 let mut s: [u8; 256] = [0; 256];
811 for (index, value) in s.iter_mut().enumerate() {
812 *value = index as u8;
813 }
814 let mut j: u8 = 0;
815 for i in 0..256 {
816 j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
817 s.swap(i, j as usize);
818 }
819 let mut output = Vec::with_capacity(data.len());
820 let mut i: u8 = 0;
821 let mut j: u8 = 0;
822 for &byte in data {
823 i = i.wrapping_add(1);
824 j = j.wrapping_add(s[i as usize]);
825 s.swap(i as usize, j as usize);
826 let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
827 output.push(byte ^ k);
828 }
829 output
830}
831
832#[cfg(test)]
833pub(crate) mod test_helpers {
834 use super::*;
840
841 pub fn rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
842 super::rc4(key, data)
843 }
844
845 pub fn compute_file_key(
846 password: &[u8],
847 o_entry: &[u8],
848 permissions: i32,
849 id_first: &[u8],
850 key_length_bytes: usize,
851 ) -> Vec<u8> {
852 super::compute_file_key(
855 password,
856 o_entry,
857 permissions,
858 id_first,
859 key_length_bytes,
860 SecurityRevision::R3,
861 true,
862 )
863 }
864
865 pub fn compute_file_key_with_revision(
866 password: &[u8],
867 o_entry: &[u8],
868 permissions: i32,
869 id_first: &[u8],
870 key_length_bytes: usize,
871 revision: SecurityRevision,
872 ) -> Vec<u8> {
873 super::compute_file_key(
874 password,
875 o_entry,
876 permissions,
877 id_first,
878 key_length_bytes,
879 revision,
880 true,
881 )
882 }
883
884 pub fn compute_file_key_r4(
889 password: &[u8],
890 o_entry: &[u8],
891 permissions: i32,
892 id_first: &[u8],
893 encrypt_metadata: bool,
894 ) -> Vec<u8> {
895 super::compute_file_key(
896 password,
897 o_entry,
898 permissions,
899 id_first,
900 16,
901 SecurityRevision::R4,
902 encrypt_metadata,
903 )
904 }
905
906 pub fn compute_u_r3(file_key: &[u8], id_first: &[u8]) -> Vec<u8> {
911 let mut hasher = Md5::new();
912 hasher.update(PASSWORD_PADDING);
913 hasher.update(id_first);
914 let seed = hasher.finalize();
915 let mut buffer = super::rc4(file_key, &seed);
916 for i in 1u8..=19 {
917 let key: Vec<u8> = file_key.iter().map(|byte| byte ^ i).collect();
918 buffer = super::rc4(&key, &buffer);
919 }
920 buffer.resize(32, 0);
921 buffer
922 }
923
924 pub fn compute_o(
930 owner_password: &[u8],
931 user_password: &[u8],
932 revision: SecurityRevision,
933 key_length_bytes: usize,
934 ) -> Vec<u8> {
935 let padded_owner = pad_password(owner_password);
936 let mut hasher = Md5::new();
937 hasher.update(padded_owner);
938 let mut digest = hasher.finalize_reset();
939 if matches!(revision, SecurityRevision::R3 | SecurityRevision::R4) {
940 for _ in 0..50 {
941 hasher.update(&digest[..key_length_bytes]);
942 digest = hasher.finalize_reset();
943 }
944 }
945 let base_key = digest[..key_length_bytes].to_vec();
946
947 let padded_user = pad_password(user_password);
948 match revision {
949 SecurityRevision::R2 => super::rc4(&base_key, &padded_user),
950 SecurityRevision::R3 | SecurityRevision::R4 => {
951 let mut buffer = super::rc4(&base_key, &padded_user);
952 for i in 1u8..=19 {
953 let key: Vec<u8> = base_key.iter().map(|byte| byte ^ i).collect();
954 buffer = super::rc4(&key, &buffer);
955 }
956 buffer
957 }
958 SecurityRevision::R5 | SecurityRevision::R6 => {
959 panic!("compute_o is not applicable to V=5 — use compute_v5_u / compute_v5_o")
960 }
961 }
962 }
963
964 pub fn object_key(file_key: &[u8], object_number: u32, generation: u16) -> Vec<u8> {
968 let mut material = Vec::with_capacity(file_key.len() + 5);
969 material.extend_from_slice(file_key);
970 let num = object_number.to_le_bytes();
971 material.push(num[0]);
972 material.push(num[1]);
973 material.push(num[2]);
974 let gen_bytes = generation.to_le_bytes();
975 material.push(gen_bytes[0]);
976 material.push(gen_bytes[1]);
977 let digest = super::md5_bytes(&material);
978 let truncated_len = (file_key.len() + 5).min(16);
979 digest[..truncated_len].to_vec()
980 }
981
982 pub fn object_key_aes(file_key: &[u8], object_number: u32, generation: u16) -> Vec<u8> {
986 let mut material = Vec::with_capacity(file_key.len() + 9);
987 material.extend_from_slice(file_key);
988 let num = object_number.to_le_bytes();
989 material.push(num[0]);
990 material.push(num[1]);
991 material.push(num[2]);
992 let gen_bytes = generation.to_le_bytes();
993 material.push(gen_bytes[0]);
994 material.push(gen_bytes[1]);
995 material.extend_from_slice(b"sAlT");
996 let digest = super::md5_bytes(&material);
997 let truncated_len = (file_key.len() + 5).min(16);
998 digest[..truncated_len].to_vec()
999 }
1000
1001 pub fn compute_v5_u_and_ue(
1006 user_password: &[u8],
1007 validation_salt: &[u8; 8],
1008 key_salt: &[u8; 8],
1009 file_key: &[u8; 32],
1010 revision: SecurityRevision,
1011 ) -> (Vec<u8>, Vec<u8>) {
1012 let verifier = super::pdf_2_b_hash(user_password, validation_salt, None, revision);
1013 let mut u = Vec::with_capacity(48);
1014 u.extend_from_slice(&verifier[..32]);
1015 u.extend_from_slice(validation_salt);
1016 u.extend_from_slice(key_salt);
1017
1018 let intermediate = super::pdf_2_b_hash(user_password, key_salt, None, revision);
1019 let ue = aes_256_cbc_encrypt_no_pad(&intermediate[..32], &[0u8; 16], file_key);
1020 (u, ue)
1021 }
1022
1023 pub fn compute_v5_o_and_oe(
1029 owner_password: &[u8],
1030 validation_salt: &[u8; 8],
1031 key_salt: &[u8; 8],
1032 u_vector: &[u8; 48],
1033 file_key: &[u8; 32],
1034 revision: SecurityRevision,
1035 ) -> (Vec<u8>, Vec<u8>) {
1036 let verifier =
1037 super::pdf_2_b_hash(owner_password, validation_salt, Some(u_vector), revision);
1038 let mut o = Vec::with_capacity(48);
1039 o.extend_from_slice(&verifier[..32]);
1040 o.extend_from_slice(validation_salt);
1041 o.extend_from_slice(key_salt);
1042
1043 let intermediate = super::pdf_2_b_hash(owner_password, key_salt, Some(u_vector), revision);
1044 let oe = aes_256_cbc_encrypt_no_pad(&intermediate[..32], &[0u8; 16], file_key);
1045 (o, oe)
1046 }
1047
1048 pub fn aes_256_cbc_encrypt(key: &[u8], iv: &[u8; 16], plaintext: &[u8]) -> Vec<u8> {
1053 assert_eq!(key.len(), 32, "AES-256 key must be 32 bytes");
1054 let cipher = Aes256::new_from_slice(key).expect("key length validated");
1055 let pad_len = 16 - (plaintext.len() % 16);
1056 let mut padded = Vec::with_capacity(plaintext.len() + pad_len);
1057 padded.extend_from_slice(plaintext);
1058 padded.extend(std::iter::repeat_n(pad_len as u8, pad_len));
1059 let mut output = Vec::with_capacity(16 + padded.len());
1060 output.extend_from_slice(iv);
1061 let mut prev: [u8; 16] = *iv;
1062 for chunk in padded.chunks(16) {
1063 let mut buf = [0u8; 16];
1064 for ((b, plain), iv_byte) in buf.iter_mut().zip(chunk.iter()).zip(prev.iter()) {
1065 *b = plain ^ iv_byte;
1066 }
1067 let mut block = GenericArray::clone_from_slice(&buf);
1068 cipher.encrypt_block(&mut block);
1069 output.extend_from_slice(block.as_slice());
1070 prev.copy_from_slice(block.as_slice());
1071 }
1072 output
1073 }
1074
1075 fn aes_256_cbc_encrypt_no_pad(key: &[u8], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
1076 assert_eq!(key.len(), 32, "AES-256 key must be 32 bytes");
1077 assert!(data.len() % 16 == 0, "plaintext must be block-aligned");
1078 let cipher = Aes256::new_from_slice(key).expect("key length validated");
1079 let mut output = Vec::with_capacity(data.len());
1080 let mut prev: [u8; 16] = *iv;
1081 for chunk in data.chunks(16) {
1082 let mut buf = [0u8; 16];
1083 for ((b, plain), iv_byte) in buf.iter_mut().zip(chunk.iter()).zip(prev.iter()) {
1084 *b = plain ^ iv_byte;
1085 }
1086 let mut block = GenericArray::clone_from_slice(&buf);
1087 cipher.encrypt_block(&mut block);
1088 output.extend_from_slice(block.as_slice());
1089 prev.copy_from_slice(block.as_slice());
1090 }
1091 output
1092 }
1093
1094 pub fn aes_128_cbc_encrypt(key: &[u8], iv: &[u8; 16], plaintext: &[u8]) -> Vec<u8> {
1098 use aes::cipher::BlockEncrypt;
1099
1100 assert_eq!(key.len(), 16, "AES-128 key must be 16 bytes");
1101 let cipher = Aes128::new_from_slice(key).expect("key length validated");
1102
1103 let pad_len = 16 - (plaintext.len() % 16);
1105 let mut padded = Vec::with_capacity(plaintext.len() + pad_len);
1106 padded.extend_from_slice(plaintext);
1107 padded.extend(std::iter::repeat_n(pad_len as u8, pad_len));
1108
1109 let mut output = Vec::with_capacity(16 + padded.len());
1110 output.extend_from_slice(iv);
1111 let mut prev: [u8; 16] = *iv;
1112 for chunk in padded.chunks(16) {
1113 let mut block = [0u8; 16];
1114 for ((b, plain), iv_byte) in block.iter_mut().zip(chunk.iter()).zip(prev.iter()) {
1115 *b = plain ^ iv_byte;
1116 }
1117 let mut arr = GenericArray::clone_from_slice(&block);
1118 cipher.encrypt_block(&mut arr);
1119 output.extend_from_slice(arr.as_slice());
1120 prev.copy_from_slice(arr.as_slice());
1121 }
1122 output
1123 }
1124}
1125
1126#[cfg(test)]
1127mod tests {
1128 use super::*;
1129
1130 #[test]
1131 fn rc4_empty_input_returns_empty() {
1132 assert_eq!(rc4(b"Key", b""), Vec::<u8>::new());
1133 }
1134
1135 #[test]
1136 fn rc4_matches_known_vector() {
1137 let key = b"Key";
1139 let plaintext = b"Plaintext";
1140 let encrypted = rc4(key, plaintext);
1141 let decrypted = rc4(key, &encrypted);
1143 assert_eq!(decrypted, plaintext);
1144 assert_eq!(
1146 encrypted,
1147 [0xBB, 0xF3, 0x16, 0xE8, 0xD9, 0x40, 0xAF, 0x0A, 0xD3]
1148 );
1149 }
1150
1151 #[test]
1152 fn pad_password_short_pads_with_padding_string() {
1153 let padded = pad_password(b"ab");
1154 assert_eq!(padded[0], b'a');
1155 assert_eq!(padded[1], b'b');
1156 assert_eq!(padded[2], PASSWORD_PADDING[0]);
1157 assert_eq!(padded[31], PASSWORD_PADDING[29]);
1158 }
1159
1160 #[test]
1161 fn pad_password_truncates_to_32_bytes() {
1162 let long = vec![b'x'; 64];
1163 let padded = pad_password(&long);
1164 assert_eq!(padded, [b'x'; 32]);
1165 }
1166
1167 fn build_encrypt_dict_r3(
1168 o_entry: Vec<u8>,
1169 u_entry: Vec<u8>,
1170 permissions: i32,
1171 ) -> PdfDictionary {
1172 let mut dict = PdfDictionary::default();
1173 dict.insert("Filter".to_string(), PdfValue::Name("Standard".to_string()));
1174 dict.insert("V".to_string(), PdfValue::Integer(2));
1175 dict.insert("R".to_string(), PdfValue::Integer(3));
1176 dict.insert("Length".to_string(), PdfValue::Integer(128));
1177 dict.insert(
1178 "O".to_string(),
1179 PdfValue::String(crate::types::PdfString(o_entry)),
1180 );
1181 dict.insert(
1182 "U".to_string(),
1183 PdfValue::String(crate::types::PdfString(u_entry)),
1184 );
1185 dict.insert("P".to_string(), PdfValue::Integer(permissions as i64));
1186 dict
1187 }
1188
1189 fn build_r3_handler_inputs(
1190 user_password: &[u8],
1191 owner_password: &[u8],
1192 id_first: &[u8],
1193 ) -> (PdfDictionary, Vec<u8>) {
1194 let key_length_bytes = 16;
1195 let permissions: i32 = -4;
1196 let o = test_helpers::compute_o(
1197 owner_password,
1198 user_password,
1199 SecurityRevision::R3,
1200 key_length_bytes,
1201 );
1202 let file_key = test_helpers::compute_file_key(
1203 user_password,
1204 &o,
1205 permissions,
1206 id_first,
1207 key_length_bytes,
1208 );
1209 let u = test_helpers::compute_u_r3(&file_key, id_first);
1210 (build_encrypt_dict_r3(o, u, permissions), file_key)
1211 }
1212
1213 #[test]
1214 fn open_authenticates_user_password() {
1215 let id_first = b"synthetic-id-0123456789abcdef";
1216 let (dict, expected_file_key) = build_r3_handler_inputs(b"userpw", b"ownerpw", id_first);
1217 let handler = StandardSecurityHandler::open(&dict, id_first, b"userpw")
1218 .expect("open succeeds")
1219 .expect("user password authenticates");
1220 assert_eq!(handler.file_key, expected_file_key);
1221 }
1222
1223 #[test]
1224 fn open_authenticates_owner_password() {
1225 let id_first = b"synthetic-id-0123456789abcdef";
1226 let (dict, expected_file_key) = build_r3_handler_inputs(b"userpw", b"ownerpw", id_first);
1227 let handler = StandardSecurityHandler::open(&dict, id_first, b"ownerpw")
1228 .expect("open succeeds")
1229 .expect("owner password authenticates");
1230 assert_eq!(handler.file_key, expected_file_key);
1233 }
1234
1235 #[test]
1236 fn open_rejects_wrong_password() {
1237 let id_first = b"synthetic-id-0123456789abcdef";
1238 let (dict, _) = build_r3_handler_inputs(b"userpw", b"ownerpw", id_first);
1239 let result = StandardSecurityHandler::open(&dict, id_first, b"wrongpw")
1240 .expect("open does not fail, only reports authentication");
1241 assert!(result.is_none());
1242 }
1243
1244 #[test]
1245 fn open_accepts_utf8_password() {
1246 let id_first = b"synthetic-id-0123456789abcdef";
1247 let password = "pässwörd".as_bytes();
1248 let (dict, _) = build_r3_handler_inputs(password, b"ownerpw", id_first);
1249 let handler = StandardSecurityHandler::open(&dict, id_first, password)
1250 .expect("open succeeds")
1251 .expect("UTF-8 password authenticates");
1252 assert_eq!(handler.file_key.len(), 16);
1253 }
1254
1255 fn build_encrypt_dict_v4_aesv2(
1256 o_entry: Vec<u8>,
1257 u_entry: Vec<u8>,
1258 permissions: i32,
1259 encrypt_metadata: Option<bool>,
1260 ) -> PdfDictionary {
1261 let mut std_cf = PdfDictionary::default();
1262 std_cf.insert("CFM".to_string(), PdfValue::Name("AESV2".to_string()));
1263 std_cf.insert("Length".to_string(), PdfValue::Integer(16));
1264 std_cf.insert(
1265 "AuthEvent".to_string(),
1266 PdfValue::Name("DocOpen".to_string()),
1267 );
1268
1269 let mut cf = PdfDictionary::default();
1270 cf.insert("StdCF".to_string(), PdfValue::Dictionary(std_cf));
1271
1272 let mut dict = PdfDictionary::default();
1273 dict.insert("Filter".to_string(), PdfValue::Name("Standard".to_string()));
1274 dict.insert("V".to_string(), PdfValue::Integer(4));
1275 dict.insert("R".to_string(), PdfValue::Integer(4));
1276 dict.insert("Length".to_string(), PdfValue::Integer(128));
1277 dict.insert("CF".to_string(), PdfValue::Dictionary(cf));
1278 dict.insert("StmF".to_string(), PdfValue::Name("StdCF".to_string()));
1279 dict.insert("StrF".to_string(), PdfValue::Name("StdCF".to_string()));
1280 dict.insert(
1281 "O".to_string(),
1282 PdfValue::String(crate::types::PdfString(o_entry)),
1283 );
1284 dict.insert(
1285 "U".to_string(),
1286 PdfValue::String(crate::types::PdfString(u_entry)),
1287 );
1288 dict.insert("P".to_string(), PdfValue::Integer(permissions as i64));
1289 if let Some(value) = encrypt_metadata {
1290 dict.insert("EncryptMetadata".to_string(), PdfValue::Bool(value));
1291 }
1292 dict
1293 }
1294
1295 fn build_v4_handler_inputs(
1296 user_password: &[u8],
1297 owner_password: &[u8],
1298 id_first: &[u8],
1299 encrypt_metadata: Option<bool>,
1300 ) -> (PdfDictionary, Vec<u8>) {
1301 let permissions: i32 = -4;
1302 let o = test_helpers::compute_o(owner_password, user_password, SecurityRevision::R4, 16);
1303 let file_key = test_helpers::compute_file_key_r4(
1304 user_password,
1305 &o,
1306 permissions,
1307 id_first,
1308 encrypt_metadata.unwrap_or(true),
1309 );
1310 let u = test_helpers::compute_u_r3(&file_key, id_first);
1311 (
1312 build_encrypt_dict_v4_aesv2(o, u, permissions, encrypt_metadata),
1313 file_key,
1314 )
1315 }
1316
1317 #[test]
1318 fn open_v4_aesv2_handler_authenticates_user_password() {
1319 let id_first = b"v4-synthetic-id-0123456789";
1320 let (dict, expected_file_key) =
1321 build_v4_handler_inputs(b"userpw", b"ownerpw", id_first, None);
1322 let handler = StandardSecurityHandler::open(&dict, id_first, b"userpw")
1323 .expect("open succeeds")
1324 .expect("user password authenticates on V=4");
1325 assert_eq!(handler.file_key, expected_file_key);
1326 assert_eq!(handler.string_method, CryptMethod::AesV2);
1327 assert_eq!(handler.stream_method, CryptMethod::AesV2);
1328 assert!(handler.encrypt_metadata);
1329 }
1330
1331 #[test]
1332 fn open_v4_aesv2_handler_authenticates_owner_password() {
1333 let id_first = b"v4-synthetic-id-0123456789";
1334 let (dict, expected_file_key) =
1335 build_v4_handler_inputs(b"userpw", b"ownerpw", id_first, None);
1336 let handler = StandardSecurityHandler::open(&dict, id_first, b"ownerpw")
1337 .expect("open succeeds")
1338 .expect("owner password authenticates on V=4");
1339 assert_eq!(handler.file_key, expected_file_key);
1340 }
1341
1342 #[test]
1343 fn open_v4_honours_encrypt_metadata_false() {
1344 let id_first = b"v4-metadata-id";
1345 let (dict, _) = build_v4_handler_inputs(b"", b"ownerpw", id_first, Some(false));
1346 let handler = StandardSecurityHandler::open(&dict, id_first, b"")
1347 .expect("open succeeds")
1348 .expect("empty password authenticates");
1349 assert!(!handler.encrypts_metadata());
1350 }
1351
1352 #[test]
1353 fn open_v4_identity_crypt_filter_is_passthrough() {
1354 let id_first = b"v4-identity-id";
1355 let (dict_v4, _) = build_v4_handler_inputs(b"", b"ownerpw", id_first, None);
1356 let mut dict = dict_v4;
1357 dict.insert("StrF".to_string(), PdfValue::Name("Identity".to_string()));
1358 dict.insert("StmF".to_string(), PdfValue::Name("Identity".to_string()));
1359
1360 let handler = StandardSecurityHandler::open(&dict, id_first, b"")
1361 .expect("open succeeds")
1362 .expect("empty password authenticates");
1363 assert_eq!(handler.string_method, CryptMethod::Identity);
1364 assert_eq!(handler.stream_method, CryptMethod::Identity);
1365
1366 let ciphertext = b"hello";
1367 let plaintext = handler
1368 .decrypt_bytes(ciphertext, ObjectRef::new(4, 0), BytesKind::Stream)
1369 .expect("identity passes bytes through");
1370 assert_eq!(plaintext, ciphertext);
1371 }
1372
1373 #[test]
1374 fn open_v4_rejects_unsupported_cfm() {
1375 let id_first = b"v4-unsupported-id";
1376
1377 let (dict_v4, _) = build_v4_handler_inputs(b"", b"ownerpw", id_first, None);
1378 let mut dict = dict_v4;
1379 let mut std_cf = PdfDictionary::default();
1380 std_cf.insert("CFM".to_string(), PdfValue::Name("AESV4".to_string()));
1385 std_cf.insert("Length".to_string(), PdfValue::Integer(32));
1386 let mut cf = PdfDictionary::default();
1387 cf.insert("StdCF".to_string(), PdfValue::Dictionary(std_cf));
1388 dict.insert("CF".to_string(), PdfValue::Dictionary(cf));
1389
1390 let error = StandardSecurityHandler::open(&dict, id_first, b"")
1391 .expect_err("unknown CFM must be rejected as unsupported");
1392 assert!(matches!(error, PdfError::Unsupported(_)), "got {error:?}");
1393 }
1394
1395 #[test]
1396 fn aes_128_cbc_round_trip() {
1397 let key = [0x11u8; 16];
1398 let iv = [0x22u8; 16];
1399 let plaintext = b"redact me, please";
1400 let ciphertext = test_helpers::aes_128_cbc_encrypt(&key, &iv, plaintext);
1401 let decrypted = aes_128_cbc_decrypt(&key, &ciphertext).expect("round trip succeeds");
1402 assert_eq!(decrypted, plaintext);
1403 }
1404
1405 #[test]
1406 fn aes_128_cbc_rejects_bad_pkcs7_padding() {
1407 let key = [0x11u8; 16];
1408 let iv = [0x22u8; 16];
1409 let plaintext = b"abcdef";
1410 let mut ciphertext = test_helpers::aes_128_cbc_encrypt(&key, &iv, plaintext);
1411 let last = ciphertext.len() - 1;
1414 ciphertext[last] ^= 0xFF;
1415 let error =
1416 aes_128_cbc_decrypt(&key, &ciphertext).expect_err("corrupted padding must be rejected");
1417 assert!(matches!(error, PdfError::Corrupt(_)), "got {error:?}");
1418 }
1419
1420 #[test]
1421 fn aes_128_cbc_rejects_short_ciphertext() {
1422 let key = [0x11u8; 16];
1423 let error = aes_128_cbc_decrypt(&key, &[0u8; 16])
1424 .expect_err("ciphertext shorter than IV+1 block must be rejected");
1425 assert!(matches!(error, PdfError::Corrupt(_)), "got {error:?}");
1426 }
1427
1428 fn build_encrypt_dict_v5_aesv3(
1429 o: Vec<u8>,
1430 u: Vec<u8>,
1431 oe: Vec<u8>,
1432 ue: Vec<u8>,
1433 permissions: i32,
1434 perms: Option<Vec<u8>>,
1435 revision: SecurityRevision,
1436 ) -> PdfDictionary {
1437 let mut std_cf = PdfDictionary::default();
1438 std_cf.insert("CFM".to_string(), PdfValue::Name("AESV3".to_string()));
1439 std_cf.insert("Length".to_string(), PdfValue::Integer(32));
1440 std_cf.insert(
1441 "AuthEvent".to_string(),
1442 PdfValue::Name("DocOpen".to_string()),
1443 );
1444
1445 let mut cf = PdfDictionary::default();
1446 cf.insert("StdCF".to_string(), PdfValue::Dictionary(std_cf));
1447
1448 let r_value = match revision {
1449 SecurityRevision::R5 => 5,
1450 SecurityRevision::R6 => 6,
1451 _ => panic!("test helper only supports R5 / R6"),
1452 };
1453
1454 let mut dict = PdfDictionary::default();
1455 dict.insert("Filter".to_string(), PdfValue::Name("Standard".to_string()));
1456 dict.insert("V".to_string(), PdfValue::Integer(5));
1457 dict.insert("R".to_string(), PdfValue::Integer(r_value));
1458 dict.insert("Length".to_string(), PdfValue::Integer(256));
1459 dict.insert("CF".to_string(), PdfValue::Dictionary(cf));
1460 dict.insert("StmF".to_string(), PdfValue::Name("StdCF".to_string()));
1461 dict.insert("StrF".to_string(), PdfValue::Name("StdCF".to_string()));
1462 dict.insert(
1463 "O".to_string(),
1464 PdfValue::String(crate::types::PdfString(o)),
1465 );
1466 dict.insert(
1467 "U".to_string(),
1468 PdfValue::String(crate::types::PdfString(u)),
1469 );
1470 dict.insert(
1471 "OE".to_string(),
1472 PdfValue::String(crate::types::PdfString(oe)),
1473 );
1474 dict.insert(
1475 "UE".to_string(),
1476 PdfValue::String(crate::types::PdfString(ue)),
1477 );
1478 dict.insert("P".to_string(), PdfValue::Integer(permissions as i64));
1479 if let Some(value) = perms {
1480 dict.insert(
1481 "Perms".to_string(),
1482 PdfValue::String(crate::types::PdfString(value)),
1483 );
1484 }
1485 dict
1486 }
1487
1488 fn build_v5_handler_inputs(
1489 user_password: &[u8],
1490 owner_password: &[u8],
1491 revision: SecurityRevision,
1492 ) -> (PdfDictionary, [u8; 32]) {
1493 let file_key = [0x13u8; 32];
1494 let u_validation_salt = [0xAAu8; 8];
1495 let u_key_salt = [0xBBu8; 8];
1496 let o_validation_salt = [0xCCu8; 8];
1497 let o_key_salt = [0xDDu8; 8];
1498
1499 let (u, ue) = test_helpers::compute_v5_u_and_ue(
1500 user_password,
1501 &u_validation_salt,
1502 &u_key_salt,
1503 &file_key,
1504 revision,
1505 );
1506 let u_vector: [u8; 48] = u.as_slice().try_into().expect("U is 48 bytes");
1507 let (o, oe) = test_helpers::compute_v5_o_and_oe(
1508 owner_password,
1509 &o_validation_salt,
1510 &o_key_salt,
1511 &u_vector,
1512 &file_key,
1513 revision,
1514 );
1515
1516 (
1517 build_encrypt_dict_v5_aesv3(o, u, oe, ue, -4, None, revision),
1518 file_key,
1519 )
1520 }
1521
1522 #[test]
1523 fn open_v5_r6_authenticates_user_password() {
1524 let (dict, expected_file_key) =
1525 build_v5_handler_inputs(b"userpw", b"ownerpw", SecurityRevision::R6);
1526 let handler = StandardSecurityHandler::open(&dict, b"", b"userpw")
1527 .expect("open succeeds")
1528 .expect("user password authenticates on V=5 / R=6");
1529 assert_eq!(handler.file_key, expected_file_key);
1530 assert_eq!(handler.string_method, CryptMethod::AesV3);
1531 assert_eq!(handler.stream_method, CryptMethod::AesV3);
1532 }
1533
1534 #[test]
1535 fn open_v5_r6_authenticates_owner_password() {
1536 let (dict, expected_file_key) =
1537 build_v5_handler_inputs(b"userpw", b"ownerpw", SecurityRevision::R6);
1538 let handler = StandardSecurityHandler::open(&dict, b"", b"ownerpw")
1539 .expect("open succeeds")
1540 .expect("owner password authenticates on V=5 / R=6");
1541 assert_eq!(handler.file_key, expected_file_key);
1542 }
1543
1544 #[test]
1545 fn open_v5_r6_rejects_wrong_password() {
1546 let (dict, _) = build_v5_handler_inputs(b"userpw", b"ownerpw", SecurityRevision::R6);
1547 let result = StandardSecurityHandler::open(&dict, b"", b"wrongpw")
1548 .expect("open does not fail, only reports authentication");
1549 assert!(result.is_none());
1550 }
1551
1552 #[test]
1553 fn open_v5_r5_authenticates_user_password() {
1554 let (dict, expected_file_key) =
1555 build_v5_handler_inputs(b"userpw", b"ownerpw", SecurityRevision::R5);
1556 let handler = StandardSecurityHandler::open(&dict, b"", b"userpw")
1557 .expect("open succeeds")
1558 .expect("user password authenticates on V=5 / R=5");
1559 assert_eq!(handler.file_key, expected_file_key);
1560 }
1561
1562 #[test]
1563 fn open_v5_r5_empty_password_authenticates() {
1564 let (dict, _) = build_v5_handler_inputs(b"", b"ownerpw", SecurityRevision::R5);
1565 let handler = StandardSecurityHandler::open(&dict, b"", b"")
1566 .expect("open succeeds")
1567 .expect("empty password authenticates on V=5 / R=5");
1568 assert_eq!(handler.string_method, CryptMethod::AesV3);
1569 }
1570
1571 #[test]
1572 fn aes_256_cbc_round_trip_through_handler() {
1573 let key = [0x13u8; 32];
1574 let iv = [0x77u8; 16];
1575 let plaintext = b"top secret V=5 content";
1576 let ciphertext = test_helpers::aes_256_cbc_encrypt(&key, &iv, plaintext);
1577 let decrypted = aes_256_cbc_decrypt(&key, &ciphertext).expect("round trip succeeds");
1578 assert_eq!(decrypted, plaintext);
1579 }
1580
1581 #[test]
1582 fn open_r2_authenticates_owner_password() {
1583 let id_first = b"r2-synthetic-id";
1586 let user_password = b"u2";
1587 let owner_password = b"o2";
1588 let key_length_bytes = 5; let permissions: i32 = -4;
1590 let o = test_helpers::compute_o(
1591 owner_password,
1592 user_password,
1593 SecurityRevision::R2,
1594 key_length_bytes,
1595 );
1596 let file_key = test_helpers::compute_file_key_with_revision(
1597 user_password,
1598 &o,
1599 permissions,
1600 id_first,
1601 key_length_bytes,
1602 SecurityRevision::R2,
1603 );
1604 let u = test_helpers::rc4(&file_key, &PASSWORD_PADDING);
1606
1607 let mut dict = PdfDictionary::default();
1608 dict.insert("Filter".to_string(), PdfValue::Name("Standard".to_string()));
1609 dict.insert("V".to_string(), PdfValue::Integer(1));
1610 dict.insert("R".to_string(), PdfValue::Integer(2));
1611 dict.insert("Length".to_string(), PdfValue::Integer(40));
1612 dict.insert(
1613 "O".to_string(),
1614 PdfValue::String(crate::types::PdfString(o)),
1615 );
1616 dict.insert(
1617 "U".to_string(),
1618 PdfValue::String(crate::types::PdfString(u)),
1619 );
1620 dict.insert("P".to_string(), PdfValue::Integer(permissions as i64));
1621
1622 let handler = StandardSecurityHandler::open(&dict, id_first, owner_password)
1623 .expect("open succeeds")
1624 .expect("owner password authenticates on R=2");
1625 assert_eq!(handler.file_key, file_key);
1626 }
1627}