sequoia_openpgp/serialize/stream/padding.rs
1//! Padding for OpenPGP messages.
2//!
3//! To reduce the amount of information leaked via the message length,
4//! encrypted OpenPGP messages (see [Section 10.3 of RFC 9580]) should
5//! be padded.
6//!
7//! [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3
8//!
9//! To pad a message using the streaming serialization interface, the
10//! [`Padder`] needs to be inserted into the writing stack between the
11//! [`Encryptor`] and [`Signer`]. This is illustrated in this
12//! [example].
13//!
14//! [`Encryptor`]: super::Encryptor
15//! [`Signer`]: super::Signer
16//! [example]: Padder#examples
17//!
18//! # Padding in OpenPGP
19//!
20//! RFC9580 introduced a [padding packet] that will be emitted when
21//! composing an RFC9580 message. Unfortunately, RFC4880 does not
22//! have a robust way to pad messages. Therefore, when composing an
23//! RFC4880 message, the message will not be padded.
24//!
25//! [padding packet]: https://www.rfc-editor.org/rfc/rfc9580.html#name-padding-packet-type-id-21
26//!
27//! To be effective, the padding layer must be placed inside the
28//! encryption container. To increase compatibility, the padding
29//! layer must not be signed. That is to say, the message structure
30//! should be `(encryption (ops literal signature padding))`.
31use std::fmt;
32use std::io;
33
34use crate::{
35 Profile,
36 Result,
37 packet::prelude::*,
38};
39use crate::serialize::{
40 Marshal,
41 stream::{
42 writer,
43 Cookie,
44 Message,
45 Private,
46 },
47};
48
49/// Pads a packet stream.
50///
51/// Writes a compressed data packet containing all packets written to
52/// this writer, and pads it according to the given policy.
53///
54/// The policy is a `Fn(u64) -> u64`, that given the number of bytes
55/// written to this writer `N`, computes the size the compression
56/// container should be padded up to. It is an error to return a
57/// number that is smaller than `N`.
58///
59/// # Compatibility
60///
61/// RFC9580 introduced a [padding packet] that will be emitted when
62/// composing an RFC9580 message. Unfortunately, RFC4880 does not
63/// have a robust way to pad messages. Therefore, when composing an
64/// RFC4880 message, the message will not be padded.
65///
66/// [padding packet]: https://www.rfc-editor.org/rfc/rfc9580.html#name-padding-packet-type-id-21
67/// # Examples
68///
69/// This example illustrates the use of `Padder` with the [Padmé]
70/// policy. Note that for brevity, the encryption and signature
71/// filters are omitted.
72///
73/// [Padmé]: padme()
74///
75/// ```
76/// use std::io::Write;
77/// use sequoia_openpgp as openpgp;
78/// use openpgp::serialize::stream::{Message, LiteralWriter};
79/// use openpgp::serialize::stream::padding::Padder;
80/// use openpgp::types::CompressionAlgorithm;
81/// # fn main() -> sequoia_openpgp::Result<()> {
82///
83/// let mut unpadded = vec![];
84/// {
85/// let message = Message::new(&mut unpadded);
86/// // XXX: Insert Encryptor here.
87/// // XXX: Insert Signer here.
88/// let mut message = LiteralWriter::new(message).build()?;
89/// message.write_all(b"Hello world.")?;
90/// message.finalize()?;
91/// }
92///
93/// let mut padded = vec![];
94/// {
95/// let message = Message::new(&mut padded);
96/// // XXX: Insert Encryptor here.
97/// let message = Padder::new(message).build()?;
98/// // XXX: Insert Signer here.
99/// let mut message = LiteralWriter::new(message).build()?;
100/// message.write_all(b"Hello world.")?;
101/// message.finalize()?;
102/// }
103/// # Ok(())
104/// # }
105pub struct Padder<'a, 'p: 'a> {
106 inner: writer::BoxStack<'a, Cookie>,
107 policy: Box<dyn Fn(u64) -> u64 + Send + Sync + 'p>,
108 cookie: Cookie,
109}
110assert_send_and_sync!(Padder<'_, '_>);
111
112impl<'a, 'p> Padder<'a, 'p> {
113 /// Creates a new padder with the given policy.
114 ///
115 /// # Examples
116 ///
117 /// This example illustrates the use of `Padder` with the [Padmé]
118 /// policy.
119 ///
120 /// [Padmé]: padme()
121 ///
122 /// The most useful filter to push to the writer stack next is the
123 /// [`Signer`] or the [`LiteralWriter`]. Finally, literal data
124 /// *must* be wrapped using the [`LiteralWriter`].
125 ///
126 /// [`Signer`]: super::Signer
127 /// [`LiteralWriter`]: super::LiteralWriter
128 ///
129 /// ```
130 /// # fn main() -> sequoia_openpgp::Result<()> {
131 /// use sequoia_openpgp as openpgp;
132 /// use openpgp::serialize::stream::padding::Padder;
133 ///
134 /// # let message = openpgp::serialize::stream::Message::new(vec![]);
135 /// // XXX: Insert Encryptor here.
136 /// let message = Padder::new(message).build()?;
137 /// // XXX: Optionally add a `Signer` here.
138 /// // XXX: Add a `LiteralWriter` here.
139 /// # let _ = message;
140 /// # Ok(()) }
141 /// ```
142 pub fn new(inner: Message<'a>) -> Self {
143 let level = inner.as_ref().cookie_ref().level;
144 let cookie = Cookie::new(level + 1);
145
146 Self {
147 inner: writer::BoxStack::from(inner),
148 policy: Box::new(padme),
149 cookie,
150 }
151 }
152
153 /// Sets padding policy, returning the padder.
154 ///
155 /// # Examples
156 ///
157 /// This example illustrates the use of `Padder` with an explicit policy.
158 ///
159 /// ```
160 /// # fn main() -> sequoia_openpgp::Result<()> {
161 /// use sequoia_openpgp as openpgp;
162 /// use openpgp::serialize::stream::padding::{Padder, padme};
163 ///
164 /// # let message = openpgp::serialize::stream::Message::new(vec![]);
165 /// // XXX: Insert Encryptor here.
166 /// let message = Padder::new(message).with_policy(padme).build()?;
167 /// // XXX: Optionally add a `Signer` here.
168 /// // XXX: Add a `LiteralWriter` here.
169 /// # let _ = message;
170 /// # Ok(()) }
171 /// ```
172 pub fn with_policy<P>(mut self, p: P) -> Self
173 where
174 P: Fn(u64) -> u64 + Send + Sync + 'p,
175 {
176 self.policy = Box::new(p);
177 self
178 }
179
180 /// Builds the padder, returning the writer stack.
181 ///
182 /// # Examples
183 ///
184 /// This example illustrates the use of `Padder` with the [Padmé]
185 /// policy.
186 ///
187 /// [Padmé]: padme()
188 ///
189 /// The most useful filter to push to the writer stack next is the
190 /// [`Signer`] or the [`LiteralWriter`]. Finally, literal data
191 /// *must* be wrapped using the [`LiteralWriter`].
192 ///
193 /// [`Signer`]: super::Signer
194 /// [`LiteralWriter`]: super::LiteralWriter
195 ///
196 /// ```
197 /// # fn main() -> sequoia_openpgp::Result<()> {
198 /// use sequoia_openpgp as openpgp;
199 /// use openpgp::serialize::stream::padding::Padder;
200 ///
201 /// # let message = openpgp::serialize::stream::Message::new(vec![]);
202 /// // XXX: Insert Encryptor here.
203 /// let message = Padder::new(message).build()?;
204 /// // XXX: Optionally add a `Signer` here.
205 /// // XXX: Add a `LiteralWriter` here.
206 /// # let _ = message;
207 /// # Ok(()) }
208 /// ```
209 pub fn build(self) -> Result<Message<'a>> {
210 Ok(Message::from(Box::new(self)))
211 }
212}
213
214impl<'a, 'p> fmt::Debug for Padder<'a, 'p> {
215 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
216 f.debug_struct("Padder")
217 .field("inner", &self.inner)
218 .field("cookie", &self.cookie)
219 .finish()
220 }
221}
222
223impl<'a, 'p> io::Write for Padder<'a, 'p> {
224 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
225 self.inner.write(buf)
226 }
227
228 fn flush(&mut self) -> io::Result<()> {
229 self.inner.flush()
230 }
231}
232
233impl<'a, 'p> writer::Stackable<'a, Cookie> for Padder<'a, 'p>
234{
235 fn into_inner(mut self: Box<Self>)
236 -> Result<Option<writer::BoxStack<'a, Cookie>>>
237 {
238 let enabled = writer::map(
239 self.as_ref(),
240 |w| match w.cookie_ref().private {
241 Private::Encryptor { profile, .. } =>
242 Some(profile == Profile::RFC9580),
243 _ => None,
244 })
245 .unwrap_or(false);
246
247 if enabled {
248 // Make a note of the amount of data written to this
249 // filter.
250 let size = self.position();
251
252 // Compute the amount of padding required according to the
253 // given policy.
254 let padded_size = (self.policy)(size);
255 if padded_size < size {
256 return Err(crate::Error::InvalidOperation(
257 format!("Padding policy({}) returned {}: \
258 smaller than argument",
259 size, padded_size)).into());
260 }
261 let amount = padded_size - size;
262
263 // Write 'amount' of padding.
264 Packet::from(Padding::new(amount.try_into()
265 .unwrap_or(usize::MAX))?)
266 .serialize(&mut self)?;
267 }
268
269 Ok(Some(self.inner))
270 }
271 fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, Cookie>>> {
272 unreachable!("Only implemented by Signer")
273 }
274 /// Sets the inner stackable.
275 fn mount(&mut self, _new: writer::BoxStack<'a, Cookie>) {
276 unreachable!("Only implemented by Signer")
277 }
278 fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, Cookie> + Send + Sync)> {
279 Some(self.inner.as_ref())
280 }
281 fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, Cookie> + Send + Sync)> {
282 Some(self.inner.as_mut())
283 }
284 fn cookie_set(&mut self, cookie: Cookie) -> Cookie {
285 std::mem::replace(&mut self.cookie, cookie)
286 }
287 fn cookie_ref(&self) -> &Cookie {
288 &self.cookie
289 }
290 fn cookie_mut(&mut self) -> &mut Cookie {
291 &mut self.cookie
292 }
293 fn position(&self) -> u64 {
294 self.inner.position()
295 }
296}
297
298/// Padmé padding scheme.
299///
300/// Padmé leaks at most O(log log M) bits of information (with M being
301/// the maximum length of all messages) with an overhead of at most
302/// 12%, decreasing with message size.
303///
304/// This scheme leaks the same order of information as padding to the
305/// next power of two, while avoiding an overhead of up to 100%.
306///
307/// See Section 4 of [Reducing Metadata Leakage from Encrypted Files
308/// and Communication with
309/// PURBs](https://bford.info/pub/sec/purb.pdf).
310///
311/// This function is meant to be used with [`Padder`], see this
312/// [example].
313///
314/// [example]: Padder#examples
315pub fn padme(l: u64) -> u64 {
316 if l < 2 {
317 return 1; // Avoid cornercase.
318 }
319
320 let e = log2(l); // l's floating-point exponent
321 let s = log2(e as u64) + 1; // # of bits to represent e
322 let z = e - s; // # of low bits to set to 0
323 let m = (1 << z) - 1; // mask of z 1's in LSB
324 (l + (m as u64)) & !(m as u64) // round up using mask m to clear last z bits
325}
326
327/// Compute the log2 of an integer. (This is simply the most
328/// significant bit.) Note: log2(0) = -Inf, but this function returns
329/// log2(0) as 0 (which is the closest number that we can represent).
330fn log2(x: u64) -> usize {
331 if x == 0 {
332 0
333 } else {
334 63 - x.leading_zeros() as usize
335 }
336}
337
338#[cfg(test)]
339mod test {
340 use super::*;
341
342 #[test]
343 fn log2_test() {
344 for i in 0..64 {
345 assert_eq!(log2(1u64 << i), i);
346 if i > 0 {
347 assert_eq!(log2((1u64 << i) - 1), i - 1);
348 assert_eq!(log2((1u64 << i) + 1), i);
349 }
350 }
351 }
352
353 fn padme_multiplicative_overhead(p: u64) -> f32 {
354 let c = padme(p);
355 let (p, c) = (p as f32, c as f32);
356 (c - p) / p
357 }
358
359 /// Experimentally, we observe the maximum overhead to be ~11.63%
360 /// when padding 129 bytes to 144.
361 const MAX_OVERHEAD: f32 = 0.1163;
362
363 #[test]
364 fn padme_max_overhead() {
365 // The paper says the maximum multiplicative overhead is
366 // 11.(11)% when padding 9 bytes to 10.
367 assert!(0.111 < padme_multiplicative_overhead(9));
368 assert!(padme_multiplicative_overhead(9) < 0.112);
369
370 // Contrary to that, we observe the maximum overhead to be
371 // ~11.63% when padding 129 bytes to 144.
372 assert!(padme_multiplicative_overhead(129) < MAX_OVERHEAD);
373 }
374
375 quickcheck! {
376 fn padme_overhead(l: u32) -> bool {
377 if l < 2 {
378 return true; // Avoid cornercase.
379 }
380
381 let o = padme_multiplicative_overhead(l as u64);
382 let l_ = l as f32;
383 let e = l_.log2().floor(); // l's floating-point exponent
384 let s = e.log2().floor() + 1.; // # of bits to represent e
385 let max_overhead = (2.0_f32.powf(e-s) - 1.) / l_;
386
387 assert!(o < MAX_OVERHEAD,
388 "padme({}) => {}: overhead {} exceeds maximum overhead {}",
389 l, padme(l.into()), o, MAX_OVERHEAD);
390 assert!(o <= max_overhead,
391 "padme({}) => {}: overhead {} exceeds maximum overhead {}",
392 l, padme(l.into()), o, max_overhead);
393 true
394 }
395 }
396
397 /// Asserts that we can consume the padded messages.
398 #[test]
399 fn roundtrip() {
400 use std::io::Write;
401 use crate::parse::Parse;
402 use crate::serialize::stream::*;
403
404 let mut msg = vec![0; rand::random::<usize>() % 1024];
405 crate::crypto::random(&mut msg).unwrap();
406
407 let mut padded = vec![];
408 {
409 let message = Message::new(&mut padded);
410 let padder = Padder::new(message).with_policy(padme).build().unwrap();
411 let mut w = LiteralWriter::new(padder).build().unwrap();
412 w.write_all(&msg).unwrap();
413 w.finalize().unwrap();
414 }
415
416 let m = crate::Message::from_bytes(&padded).unwrap();
417 assert_eq!(m.body().unwrap().body(), &msg[..]);
418 }
419
420 /// Asserts that no actual compression is done.
421 ///
422 /// We want to avoid having the size of the data stream depend on
423 /// the data's compressibility, therefore it is best to disable
424 /// the compression.
425 #[test]
426 fn no_compression() {
427 use std::io::Write;
428 use crate::serialize::stream::*;
429 const MSG: &[u8] = b"@@@@@@@@@@@@@@";
430 let mut padded = vec![];
431 {
432 let message = Message::new(&mut padded);
433 let padder = Padder::new(message).build().unwrap();
434 let mut w = LiteralWriter::new(padder).build().unwrap();
435 w.write_all(MSG).unwrap();
436 w.finalize().unwrap();
437 }
438
439 assert!(padded.windows(MSG.len()).any(|ch| ch == MSG),
440 "Could not find uncompressed message");
441 }
442}