pdfrum_edit/encrypt.rs
1//! The output-side security seam.
2//!
3//! An encrypted document saves **encrypted**, under the handler and file key
4//! the original password opened it with, so the saved file opens with that
5//! same password. Objects arrive here plaintext — the parser peeled the
6//! cipher off at fetch time — and this is where it goes back on.
7//!
8//! [`SaveOptions::remove_security`](crate::SaveOptions::remove_security)
9//! remains the explicit way to drop it: it suppresses `/Encrypt` from the
10//! trailer and passes `None` here, so nothing is enciphered.
11//!
12//! # Three things are never enciphered
13//!
14//! - **The `/Encrypt` dictionary itself.** ISO 32000-1 §7.6.1: a reader has
15//! to read `/O`, `/U` and `/Perms` before it has a key. The C++ tests for it
16//! by pointer identity against the object it is writing; we test by object
17//! number, which is the same test once the dictionary has one.
18//! - **A signature's `/Contents`.** It covers a byte range of the finished
19//! file, so re-enciphering it would invalidate the signature.
20//! - **An XMP metadata stream's payload.** ISO 32000-1 §14.3.2 requires the
21//! packet be readable by a consumer holding neither the file key nor a
22//! flate decoder. Note this is unconditional in the C++ writer — it never
23//! consults `/EncryptMetadata`, so a document declaring `true` still gets a
24//! plaintext metadata stream on save. The stream's *dictionary* is still
25//! enciphered; only the payload is exempt.
26//!
27//! The trailer is a fourth, but it never reaches this type: it is not an
28//! indirect object, and `write_classic` passes `None` outright.
29//!
30//! # Where the initialisation vectors come from
31//!
32//! AES-CBC needs a vector per payload, and under AESV3 (ISO 32000-2 §7.6.5.3)
33//! the file key enciphers every object verbatim, with no per-object
34//! derivation. The vector is therefore the only thing separating two
35//! ciphertexts under one key, and it has to satisfy both of CBC's
36//! requirements:
37//!
38//! - **Unique within a save.** Two payloads sharing a key and a vector leak
39//! the XOR of their first blocks to anyone holding the ciphertext, key or
40//! no key. [`IvSource`] mixes a per-save secret with the object number and
41//! a counter, so each payload gets its own vector however the writer
42//! enumerates objects.
43//! - **Unpredictable.** The per-save secret is 32 bytes drawn from the
44//! operating system, so a vector cannot be recomputed from anything the
45//! file itself exposes — its length, its head, its tail, or a sibling file
46//! built from the same template.
47//!
48//! An encrypted save is therefore not byte-reproducible: reproducible
49//! ciphertext is reproducible secrets. [`crate::IdSource::Fixed`] pins
50//! everything a save writes in the clear, which is what reproducibility is
51//! for.
52
53use std::cell::Cell;
54
55use pdfrum_crypt::{CryptClass, Iv, SecurityHandler};
56use pdfrum_object::ObjRef;
57use sha2::{Digest, Sha256};
58use zeroize::Zeroize;
59
60use crate::Error;
61
62/// Where a save's AES initialisation vectors come from.
63///
64/// Deliberately neither `Copy` nor `Clone`: it holds a counter, and a copy
65/// would hand two call sites the same vector sequence. Its `Debug` shows the
66/// counter and withholds the secret, which a log has no use for.
67pub struct IvSource {
68 /// Thirty-two bytes from the operating system, drawn once per save. The
69 /// vectors are unpredictable exactly because this is.
70 secret: [u8; 32],
71 /// Advanced once per vector handed out.
72 counter: Cell<u64>,
73}
74
75impl std::fmt::Debug for IvSource {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("IvSource")
78 .field("counter", &self.counter.get())
79 .finish_non_exhaustive()
80 }
81}
82
83impl Drop for IvSource {
84 fn drop(&mut self) {
85 self.secret.zeroize();
86 }
87}
88
89impl IvSource {
90 /// A source keyed by fresh operating-system randomness.
91 ///
92 /// # Errors
93 ///
94 /// [`Error::NoEntropy`] when the platform's generator is unavailable.
95 pub fn from_os() -> Result<Self, Error> {
96 let mut secret = [0u8; 32];
97 getrandom::fill(&mut secret).map_err(|_| Error::NoEntropy)?;
98 Ok(Self {
99 secret,
100 counter: Cell::new(0),
101 })
102 }
103
104 /// The next vector, advancing the counter.
105 ///
106 /// `obj` goes into the mix as well as the counter, so two saves that
107 /// enumerate objects in different orders still give each object its own
108 /// vector rather than one that depends on its position.
109 fn next(&self, obj: ObjRef) -> Iv {
110 let index = self.counter.get();
111 self.counter.set(index.wrapping_add(1));
112
113 // SHA-256 over the secret and the pair that makes this payload
114 // unique. Absorbing before emitting is what carries every input into
115 // every output byte; the vector is the leading half of the digest.
116 let mut hash = Sha256::new();
117 hash.update(self.secret);
118 hash.update(index.to_le_bytes());
119 hash.update(obj.num.to_le_bytes());
120 hash.update(obj.generation.to_le_bytes());
121 let digest = hash.finalize();
122 let mut out = [0u8; 16];
123 for (slot, byte) in out.iter_mut().zip(digest) {
124 *slot = byte;
125 }
126 Iv(out)
127 }
128}
129
130/// Enciphers the strings and stream payloads of one indirect object.
131///
132/// Per ISO 32000-1 §7.6.2 the key is derived per object from its number and
133/// generation; the generation is always 0 here because that is what the
134/// writer emits, which is also what the C++ passes.
135///
136/// Borrowed rather than owned so one handler and one vector source serve a
137/// whole save, with only the object number changing per object.
138#[derive(Debug, Clone, Copy)]
139pub struct Encryptor<'a> {
140 handler: &'a SecurityHandler,
141 ivs: &'a IvSource,
142 object_number: u32,
143}
144
145impl<'a> Encryptor<'a> {
146 /// An encryptor for the object numbered `num`.
147 #[must_use]
148 pub const fn new(handler: &'a SecurityHandler, ivs: &'a IvSource, object_number: u32) -> Self {
149 Self {
150 handler,
151 ivs,
152 object_number,
153 }
154 }
155
156 /// Whether this document's metadata stream is enciphered along with
157 /// everything else (`/EncryptMetadata`, default true).
158 ///
159 /// Read by the stream encoder, which owns the exemption; see its docs for
160 /// why we consult the flag where the C++ writer does not.
161 #[must_use]
162 pub fn encrypts_metadata(&self) -> bool {
163 self.handler.encrypt_metadata()
164 }
165
166 /// Encipher one run of bytes as a string or a stream payload.
167 ///
168 /// The class never changes the result — PDFium refuses a document whose
169 /// `/StmF` and `/StrF` differ — but naming it keeps the call site honest
170 /// about what it is writing.
171 #[must_use]
172 pub fn encrypt(&self, class: CryptClass, data: &[u8]) -> Vec<u8> {
173 let obj = ObjRef::new(self.object_number, 0);
174 self.handler.encrypt(obj, class, self.ivs.next(obj), data)
175 }
176}
177
178/// Everything a save needs to re-encipher what it writes.
179///
180/// Held by [`crate::save`] for the length of one save and handed to each
181/// object in turn. `None` for an unencrypted document and for a
182/// `remove_security` save, which is what makes "pass `None` and nothing is
183/// enciphered" the whole of the plaintext path.
184#[derive(Debug)]
185pub(crate) struct Security<'a> {
186 /// The handler the original password opened the document with.
187 pub(crate) handler: &'a SecurityHandler,
188 /// The vector source for this save.
189 pub(crate) ivs: IvSource,
190 /// The object number the `/Encrypt` dictionary will be written as — the
191 /// one object that is never enciphered.
192 pub(crate) encrypt_object: Option<u32>,
193}
194
195impl Security<'_> {
196 /// The encryptor for object `num`, or `None` when that object is the
197 /// `/Encrypt` dictionary.
198 #[must_use]
199 pub(crate) fn for_object(&self, num: u32) -> Option<Encryptor<'_>> {
200 if self.encrypt_object == Some(num) {
201 return None;
202 }
203 Some(Encryptor::new(self.handler, &self.ivs, num))
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::{Encryptor, IvSource, Security};
210 use pdfrum_crypt::{CryptClass, SecurityHandler};
211 use pdfrum_object::ObjRef;
212
213 fn handler() -> SecurityHandler {
214 SecurityHandler::AesV5 {
215 key: Box::new([0x42; 32]),
216 revision: 6,
217 permissions: 0xFFFF_FFFC,
218 owner_unlocked: false,
219 encrypt_metadata: true,
220 encoding: pdfrum_crypt::PasswordEncoding::AsGiven,
221 // No /EFF, so the embedded class takes the stream cipher —
222 // ISO 32000-1 §7.6.5 table 20's own default.
223 embedded_cipher: None,
224 strings_identity: false,
225 }
226 }
227
228 // The property CBC actually needs: no two payloads in one save share a
229 // vector, whether they belong to one object or to many.
230 #[test]
231 fn vectors_never_repeat_within_one_save() {
232 let ivs = IvSource::from_os().unwrap();
233 let mut seen = std::collections::BTreeSet::new();
234 for num in 1..40u32 {
235 for _ in 0..4 {
236 assert!(seen.insert(ivs.next(ObjRef::new(num, 0)).0), "{num}");
237 }
238 }
239 assert_eq!(seen.len(), 39 * 4);
240 }
241
242 // Unpredictability: two sources drawn from the OS share nothing, so a
243 // vector cannot be recovered from another save of the same document.
244 #[test]
245 fn two_sources_never_agree() {
246 let a = IvSource::from_os().unwrap();
247 let b = IvSource::from_os().unwrap();
248 for num in 1..8u32 {
249 assert_ne!(a.next(ObjRef::new(num, 0)), b.next(ObjRef::new(num, 0)));
250 }
251 }
252
253 // The round trip through the encryptor's own object numbering.
254 #[test]
255 fn an_encryptor_round_trips_under_its_object_number() {
256 let h = handler();
257 let ivs = IvSource::from_os().unwrap();
258 let enc = Encryptor::new(&h, &ivs, 12);
259 let payload = b"the quick brown fox".to_vec();
260 let sealed = enc.encrypt(CryptClass::String, &payload);
261 assert_ne!(sealed, payload);
262 assert_eq!(
263 h.decrypt(ObjRef::new(12, 0), CryptClass::String, &sealed),
264 payload
265 );
266 }
267
268 // The encrypt dictionary is the one object that gets no encryptor.
269 #[test]
270 fn the_encrypt_dictionary_is_never_given_an_encryptor() {
271 let h = handler();
272 let security = Security {
273 handler: &h,
274 ivs: IvSource::from_os().unwrap(),
275 encrypt_object: Some(9),
276 };
277 assert!(security.for_object(9).is_none());
278 assert!(security.for_object(8).is_some());
279 assert!(security.for_object(10).is_some());
280
281 // With no encrypt object named, every object gets one.
282 let security = Security {
283 handler: &h,
284 ivs: IvSource::from_os().unwrap(),
285 encrypt_object: None,
286 };
287 assert!(security.for_object(9).is_some());
288 }
289
290 // An unencrypted document's seam is the identity, which is what makes
291 // "pass `None`" and "pass an Identity handler" agree.
292 #[test]
293 fn the_identity_handler_writes_what_it_was_given() {
294 let h = SecurityHandler::Identity;
295 let ivs = IvSource::from_os().unwrap();
296 let enc = Encryptor::new(&h, &ivs, 4);
297 let payload = vec![0xABu8; 33];
298 assert_eq!(enc.encrypt(CryptClass::Stream, &payload), payload);
299 }
300}