1use crate::error::Result;
6use crate::object::{PdfDict, PdfObject};
7
8use super::aes_cipher;
9use super::key;
10use super::rc4;
11use super::types::{CryptMethod, EncryptionDict, Permissions, SecurityState};
12
13#[derive(Debug, Clone)]
15pub struct EncryptionConfig {
16 pub user_password: Vec<u8>,
18 pub owner_password: Vec<u8>,
20 pub permissions: Permissions,
22 pub method: EncryptionMethod,
24 pub encrypt_metadata: bool,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum EncryptionMethod {
31 RC4_128,
33 AES128,
35 AES256,
37}
38
39impl EncryptionConfig {
40 pub fn build(
46 &self,
47 file_id: &[u8],
48 ) -> Result<(SecurityState, PdfDict, Vec<PdfObject>)> {
49 match self.method {
50 EncryptionMethod::RC4_128 => self.build_r3(file_id),
51 EncryptionMethod::AES128 => self.build_r4(file_id),
52 EncryptionMethod::AES256 => self.build_r6(file_id),
53 }
54 }
55
56 fn build_r3(&self, file_id: &[u8]) -> Result<(SecurityState, PdfDict, Vec<PdfObject>)> {
57 let mut ed = EncryptionDict {
58 filter: b"Standard".to_vec(),
59 v: 2,
60 length: 128,
61 r: 3,
62 o: vec![0u8; 32],
63 u: vec![0u8; 32],
64 p: self.permissions.bits,
65 encrypt_metadata: self.encrypt_metadata,
66 oe: None,
67 ue: None,
68 perms: None,
69 cf: None,
70 stm_f: None,
71 str_f: None,
72 };
73
74 let (o, u, file_key) = key::generate_o_u_values_r234(
75 &self.user_password,
76 &self.owner_password,
77 &ed,
78 file_id,
79 );
80 ed.o = o;
81 ed.u = u;
82
83 let pdf_dict = ed.to_pdf_dict();
84 let id_array = make_id_array(file_id);
85
86 let mut state = SecurityState::new(ed, file_id.to_vec(), None);
87 state.file_key = Some(file_key);
88 state.string_method = CryptMethod::V2;
89 state.stream_method = CryptMethod::V2;
90
91 Ok((state, pdf_dict, id_array))
92 }
93
94 fn build_r4(&self, file_id: &[u8]) -> Result<(SecurityState, PdfDict, Vec<PdfObject>)> {
95 let mut ed = EncryptionDict {
96 filter: b"Standard".to_vec(),
97 v: 4,
98 length: 128,
99 r: 4,
100 o: vec![0u8; 32],
101 u: vec![0u8; 32],
102 p: self.permissions.bits,
103 encrypt_metadata: self.encrypt_metadata,
104 oe: None,
105 ue: None,
106 perms: None,
107 cf: Some(super::types::CryptFilterMap {
108 filters: vec![(
109 b"StdCF".to_vec(),
110 super::types::CryptFilter {
111 cfm: CryptMethod::AESV2,
112 key_length: 16,
113 },
114 )],
115 }),
116 stm_f: Some(b"StdCF".to_vec()),
117 str_f: Some(b"StdCF".to_vec()),
118 };
119
120 let (o, u, file_key) = key::generate_o_u_values_r234(
121 &self.user_password,
122 &self.owner_password,
123 &ed,
124 file_id,
125 );
126 ed.o = o;
127 ed.u = u;
128
129 let pdf_dict = ed.to_pdf_dict();
130 let id_array = make_id_array(file_id);
131
132 let mut state = SecurityState::new(ed, file_id.to_vec(), None);
133 state.file_key = Some(file_key);
134 state.string_method = CryptMethod::AESV2;
135 state.stream_method = CryptMethod::AESV2;
136
137 Ok((state, pdf_dict, id_array))
138 }
139
140 fn build_r6(&self, file_id: &[u8]) -> Result<(SecurityState, PdfDict, Vec<PdfObject>)> {
141 let file_key = generate_random_key();
143
144 let uvs = generate_random_salt();
146 let uks = generate_random_salt();
147 let ovs = generate_random_salt();
148 let oks = generate_random_salt();
149
150 let (o, u, oe, ue, perms) = key::generate_values_r6(
151 &self.user_password,
152 &self.owner_password,
153 self.permissions.bits,
154 self.encrypt_metadata,
155 &file_key,
156 &uvs,
157 &uks,
158 &ovs,
159 &oks,
160 );
161
162 let ed = EncryptionDict {
163 filter: b"Standard".to_vec(),
164 v: 5,
165 length: 256,
166 r: 6,
167 o,
168 u,
169 p: self.permissions.bits,
170 encrypt_metadata: self.encrypt_metadata,
171 oe: Some(oe),
172 ue: Some(ue),
173 perms: Some(perms),
174 cf: Some(super::types::CryptFilterMap {
175 filters: vec![(
176 b"StdCF".to_vec(),
177 super::types::CryptFilter {
178 cfm: CryptMethod::AESV3,
179 key_length: 32,
180 },
181 )],
182 }),
183 stm_f: Some(b"StdCF".to_vec()),
184 str_f: Some(b"StdCF".to_vec()),
185 };
186
187 let pdf_dict = ed.to_pdf_dict();
188 let id_array = make_id_array(file_id);
189
190 let mut state = SecurityState::new(ed, file_id.to_vec(), None);
191 state.file_key = Some(file_key.to_vec());
192 state.string_method = CryptMethod::AESV3;
193 state.stream_method = CryptMethod::AESV3;
194
195 Ok((state, pdf_dict, id_array))
196 }
197}
198
199pub fn encrypt_object(
201 obj: &PdfObject,
202 state: &SecurityState,
203 obj_num: u32,
204 gen_num: u16,
205) -> Result<PdfObject> {
206 let file_key = match &state.file_key {
207 Some(k) => k,
208 None => return Ok(obj.clone()),
209 };
210
211 if let Some(enc_num) = state.encrypt_obj_num {
213 if obj_num == enc_num {
214 return Ok(obj.clone());
215 }
216 }
217
218 match obj {
219 PdfObject::String(data) => {
220 let encrypted = encrypt_bytes(
221 file_key,
222 data,
223 obj_num,
224 gen_num,
225 state.string_method,
226 )?;
227 Ok(PdfObject::String(encrypted))
228 }
229 PdfObject::Stream { dict, data } => {
230 let encrypted = encrypt_bytes(
231 file_key,
232 data,
233 obj_num,
234 gen_num,
235 state.stream_method,
236 )?;
237 Ok(PdfObject::Stream {
238 dict: dict.clone(),
239 data: encrypted,
240 })
241 }
242 PdfObject::Dict(d) => {
243 let mut new_dict = PdfDict::new();
244 for (k, v) in d.iter() {
245 let encrypted_val = encrypt_object(v, state, obj_num, gen_num)?;
246 new_dict.insert(k.clone(), encrypted_val);
247 }
248 Ok(PdfObject::Dict(new_dict))
249 }
250 PdfObject::Array(arr) => {
251 let mut new_arr = Vec::with_capacity(arr.len());
252 for item in arr {
253 new_arr.push(encrypt_object(item, state, obj_num, gen_num)?);
254 }
255 Ok(PdfObject::Array(new_arr))
256 }
257 other => Ok(other.clone()),
258 }
259}
260
261fn encrypt_bytes(
263 file_key: &[u8],
264 data: &[u8],
265 obj_num: u32,
266 gen_num: u16,
267 method: CryptMethod,
268) -> Result<Vec<u8>> {
269 if data.is_empty() {
270 return Ok(Vec::new());
271 }
272
273 match method {
274 CryptMethod::None => Ok(data.to_vec()),
275 CryptMethod::V2 => {
276 let obj_key = key::compute_object_key(file_key, obj_num, gen_num, false);
277 Ok(rc4::rc4(&obj_key, data))
278 }
279 CryptMethod::AESV2 => {
280 let obj_key = key::compute_object_key(file_key, obj_num, gen_num, true);
281 let iv = generate_iv();
282 aes_cipher::encrypt_aes_cbc(&obj_key, data, &iv)
283 }
284 CryptMethod::AESV3 => {
285 let iv = generate_iv();
286 aes_cipher::encrypt_aes_cbc(file_key, data, &iv)
287 }
288 }
289}
290
291fn make_id_array(file_id: &[u8]) -> Vec<PdfObject> {
293 vec![
294 PdfObject::String(file_id.to_vec()),
295 PdfObject::String(file_id.to_vec()),
296 ]
297}
298
299fn generate_iv() -> [u8; 16] {
301 let mut iv = [0u8; 16];
302 let seed = std::time::SystemTime::now()
305 .duration_since(std::time::UNIX_EPOCH)
306 .unwrap_or_default()
307 .as_nanos();
308 let hash = {
309 use md5::Digest;
310 let mut h = md5::Md5::new();
311 h.update(seed.to_le_bytes());
312 h.update(b"justpdf-iv");
313 h.finalize()
314 };
315 iv.copy_from_slice(&hash);
316 iv
317}
318
319fn generate_random_key() -> [u8; 32] {
321 let mut key = [0u8; 32];
322 let seed = std::time::SystemTime::now()
323 .duration_since(std::time::UNIX_EPOCH)
324 .unwrap_or_default()
325 .as_nanos();
326 let hash1 = {
327 use sha2::Digest;
328 let mut h = sha2::Sha256::new();
329 h.update(seed.to_le_bytes());
330 h.update(b"justpdf-key-1");
331 h.finalize()
332 };
333 key.copy_from_slice(&hash1);
334 key
335}
336
337fn generate_random_salt() -> [u8; 8] {
339 let mut salt = [0u8; 8];
340 static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
341 let count = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
342 let seed = std::time::SystemTime::now()
343 .duration_since(std::time::UNIX_EPOCH)
344 .unwrap_or_default()
345 .as_nanos()
346 .wrapping_add(count as u128);
347 let hash = {
348 use md5::Digest;
349 let mut h = md5::Md5::new();
350 h.update(seed.to_le_bytes());
351 h.update(b"justpdf-salt");
352 h.finalize()
353 };
354 salt.copy_from_slice(&hash[..8]);
355 salt
356}
357
358pub fn generate_file_id(title: &[u8], timestamp: u64) -> Vec<u8> {
360 use md5::Digest;
361 let mut h = md5::Md5::new();
362 h.update(title);
363 h.update(timestamp.to_le_bytes());
364 h.update(b"justpdf");
365 h.finalize().to_vec()
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 fn make_state_for_encrypt(method: CryptMethod) -> SecurityState {
373 let ed = EncryptionDict {
374 filter: b"Standard".to_vec(),
375 v: if method == CryptMethod::AESV3 { 5 } else { 2 },
376 length: if method == CryptMethod::AESV3 { 256 } else { 128 },
377 r: match method {
378 CryptMethod::V2 => 3,
379 CryptMethod::AESV2 => 4,
380 CryptMethod::AESV3 => 6,
381 CryptMethod::None => 3,
382 },
383 o: vec![0u8; 32],
384 u: vec![0u8; 32],
385 p: -4,
386 encrypt_metadata: true,
387 oe: None,
388 ue: None,
389 perms: None,
390 cf: None,
391 stm_f: None,
392 str_f: None,
393 };
394
395 let key_len = if method == CryptMethod::AESV3 { 32 } else { 16 };
396 let mut state = SecurityState::new(ed, b"id".to_vec(), None);
397 state.file_key = Some(vec![0x42u8; key_len]);
398 state.string_method = method;
399 state.stream_method = method;
400 state
401 }
402
403 #[test]
404 fn test_encrypt_decrypt_rc4_roundtrip() {
405 let state = make_state_for_encrypt(CryptMethod::V2);
406
407 let original = PdfObject::String(b"Hello RC4!".to_vec());
408 let encrypted = encrypt_object(&original, &state, 1, 0).unwrap();
409 assert_ne!(encrypted, original);
410
411 let decrypted =
412 super::super::decrypt::decrypt_object(encrypted, &state, 1, 0).unwrap();
413 assert_eq!(decrypted, original);
414 }
415
416 #[test]
417 fn test_encrypt_decrypt_aes128_roundtrip() {
418 let state = make_state_for_encrypt(CryptMethod::AESV2);
419
420 let original = PdfObject::String(b"Hello AES-128!".to_vec());
421 let encrypted = encrypt_object(&original, &state, 1, 0).unwrap();
422 assert_ne!(encrypted, original);
423
424 let decrypted =
425 super::super::decrypt::decrypt_object(encrypted, &state, 1, 0).unwrap();
426 assert_eq!(decrypted, original);
427 }
428
429 #[test]
430 fn test_encrypt_decrypt_aes256_roundtrip() {
431 let state = make_state_for_encrypt(CryptMethod::AESV3);
432
433 let original = PdfObject::String(b"Hello AES-256!".to_vec());
434 let encrypted = encrypt_object(&original, &state, 1, 0).unwrap();
435 assert_ne!(encrypted, original);
436
437 let decrypted =
438 super::super::decrypt::decrypt_object(encrypted, &state, 1, 0).unwrap();
439 assert_eq!(decrypted, original);
440 }
441
442 #[test]
443 fn test_encrypt_config_r3() {
444 let config = EncryptionConfig {
445 user_password: b"user".to_vec(),
446 owner_password: b"owner".to_vec(),
447 permissions: Permissions::allow_all(),
448 method: EncryptionMethod::RC4_128,
449 encrypt_metadata: true,
450 };
451
452 let file_id = b"test-file-id-cfg";
453 let (state, pdf_dict, id_arr) = config.build(file_id).unwrap();
454
455 assert!(state.file_key.is_some());
456 assert_eq!(pdf_dict.get_i64(b"V"), Some(2));
457 assert_eq!(pdf_dict.get_i64(b"R"), Some(3));
458 assert_eq!(id_arr.len(), 2);
459 }
460
461 #[test]
462 fn test_encrypt_config_r4() {
463 let config = EncryptionConfig {
464 user_password: vec![],
465 owner_password: b"secret".to_vec(),
466 permissions: Permissions::allow_all(),
467 method: EncryptionMethod::AES128,
468 encrypt_metadata: true,
469 };
470
471 let (state, pdf_dict, _) = config.build(b"id").unwrap();
472 assert_eq!(pdf_dict.get_i64(b"V"), Some(4));
473 assert_eq!(pdf_dict.get_i64(b"R"), Some(4));
474 assert_eq!(state.string_method, CryptMethod::AESV2);
475 }
476
477 #[test]
478 fn test_encrypt_config_r6() {
479 let config = EncryptionConfig {
480 user_password: b"user256".to_vec(),
481 owner_password: b"owner256".to_vec(),
482 permissions: Permissions::allow_all(),
483 method: EncryptionMethod::AES256,
484 encrypt_metadata: true,
485 };
486
487 let (state, pdf_dict, _) = config.build(b"id256").unwrap();
488 assert_eq!(pdf_dict.get_i64(b"V"), Some(5));
489 assert_eq!(pdf_dict.get_i64(b"R"), Some(6));
490 assert_eq!(state.string_method, CryptMethod::AESV3);
491 assert_eq!(state.file_key.as_ref().unwrap().len(), 32);
492 }
493
494 #[test]
495 fn test_encrypt_skips_encrypt_dict_obj() {
496 let mut state = make_state_for_encrypt(CryptMethod::V2);
497 state.encrypt_obj_num = Some(10);
498
499 let original = PdfObject::String(b"no encrypt".to_vec());
500 let result = encrypt_object(&original, &state, 10, 0).unwrap();
501 assert_eq!(result, original); }
503
504 #[test]
505 fn test_generate_file_id() {
506 let id1 = generate_file_id(b"Doc 1", 12345);
507 let id2 = generate_file_id(b"Doc 2", 12345);
508 assert_eq!(id1.len(), 16);
509 assert_ne!(id1, id2);
510 }
511}