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}