diff options
Diffstat (limited to 'openpgp/src/packet/aed.rs')
-rw-r--r-- | openpgp/src/packet/aed.rs | 166 |
1 files changed, 115 insertions, 51 deletions
diff --git a/openpgp/src/packet/aed.rs b/openpgp/src/packet/aed.rs index 8af94f00..b7f6e58c 100644 --- a/openpgp/src/packet/aed.rs +++ b/openpgp/src/packet/aed.rs @@ -1,4 +1,77 @@ //! AEAD encrypted data packets. +//! +//! An encryption container using [Authenticated Encryption with +//! Additional Data]. +//! +//! The AED packet is a new packet specified in [Section 5.16 of RFC +//! 4880bis]. Its aim is to replace the [SEIP packet], whose security +//! has been partially compromised. SEIP's weaknesses includes its +//! use of CFB mode (e.g., EFAIL-style CFB gadgets, see Section 5.3 of +//! the [EFAIL paper]), its use of [SHA-1] for integrity protection, and +//! the ability to [downgrade SEIP packets] to much weaker SED +//! packets. +//! +//! Although the decision to use AEAD is uncontroversial, the design +//! specified in RFC 4880bis is. According to [RFC 5116], decrypted +//! AEAD data can only be released for processing after its +//! authenticity has been checked: +//! +//! > [The authenticated decryption operation] has only a single +//! > output, either a plaintext value P or a special symbol FAIL that +//! > indicates that the inputs are not authentic. +//! +//! The controversy has to do with streaming, which OpenPGP has +//! traditionally supported. Streaming a message means that the +//! amount of data that needs to be buffered when processing a message +//! is independent of the message's length. +//! +//! At first glance, the AEAD mechanism in RFC 4880bis appears to +//! support this mode of operation: instead of encrypting the whole +//! message using AEAD, which would require buffering all of the +//! plaintext when decrypting the message, the message is chunked, the +//! individual chunks are linked together, and AEAD is used to encrypt +//! and protect each individual chunk. Because the plaintext from an +//! individual chunk can be integrity checked, an implementation only +//! needs to buffer a chunk worth of data. +//! +//! Unfortunately, RFC 4880bis allows chunk sizes that are, in +//! practice, unbounded. Specifically, a chunk can be up to 4 +//! exbibytes in size. Thus when encountering messages that can't be +//! buffered, an OpenPGP implementation has a choice: it can either +//! release data that has not been integrity checked and violate RFC +//! 5116, or it can fail to process the message. As of 2020, [GnuPG] +//! and [RNP] process unauthenticated plaintext. From a user +//! perspective, it then appears that implementations that choose to +//! follow RFC 5116 are impaired: "GnuPG can decrypt it," they think, +//! "why can't Sequoia?" This creates pressure on other +//! implementations to also behave insecurely. +//! +//! [Werner argues] that AEAD is not about authenticating the data. +//! That is the purpose of the signature. The reason to introduce +//! AEAD is to get the benefits of more modern cryptography, and to be +//! able to more quickly detect rare transmission errors. Our +//! position is that an integrity check provides real protection: it +//! can detect modified ciphertext. And, if we are going to stream, +//! then this protection is essential as it protects the user from +//! real, demonstrated attacks like [EFAIL]. +//! +//! RFC 4880bis has not been finalized. So, it is still possible that +//! the AEAD mechanism will change (which is why the AED packet is +//! marked as experimental). Despite our concerns, because other +//! OpenPGP implementations already emit the AEAD packet, we provide +//! *experimental* support for it in Sequoia. +//! +//! [Authenticated Encryption with Additional Data]: https://en.wikipedia.org/wiki/Authenticated_encryption +//! [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-5.16 +//! [EFAIL paper]: https://www.usenix.org/conference/usenixsecurity18/presentation/poddebniak +//! [SHA-1]: https://sha-mbles.github.io/ +//! [SEIP packet]: https://tools.ietf.org/html/rfc4880#section-5.13 +//! [RFC 5116]: https://tools.ietf.org/html/rfc5116#section-2.2 +//! [downgrade SEIP packets]: https://mailarchive.ietf.org/arch/msg/openpgp/JLn7sL6TqikUf-cD34lN7kof7_A/ +//! [GnuPG]: https://mailarchive.ietf.org/arch/msg/openpgp/fmQgRm94jhvPLEOi0J-o7A8LpkY/ +//! [RNP]: https://github.com/rnpgp/rnp/issues/807 +//! [Werner argues]: https://mailarchive.ietf.org/arch/msg/openpgp/J428Mqq3-pHTU4C76EgP5sPkvtA +//! [EFAIL]: https://efail.de/ use crate::types::{ AEADAlgorithm, @@ -11,13 +84,31 @@ use crate::Result; /// Holds an AEAD encrypted data packet. /// -/// An AEAD encrypted data packet is a container. See [Section 5.16 -/// of RFC 4880bis] for details. +/// An AEAD encrypted data packet holds encrypted data. The data +/// contains additional OpenPGP packets. See [Section 5.16 of RFC +/// 4880bis] for details. +/// +/// An AED packet is not normally instantiated directly. In most +/// cases, you'll create one as a side-effect of encrypting a message +/// using the [streaming serializer], or parsing an encrypted message +/// using the [`PacketParser`]. +/// +/// This feature is +/// [experimental](../../index.html#experimental-features). It has +/// not been standardized and we advise users to not emit AED packets. /// /// [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.16 +/// [streaming serializer]: ../serialize/stream/index.html +/// [`PacketParser`]: ../parse/index.html +/// +/// # A note on equality /// -/// This feature is [experimental](../../index.html#experimental-features). -#[derive(Clone, Debug)] +/// An unprocessed (encrypted) `AED` packet is never considered equal +/// to a processed (decrypted) one. Likewise, a processed (decrypted) +/// packet is never considered equal to a structured (parsed) one. +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AED1 { /// CTB packet header fields. pub(crate) common: packet::Common, @@ -26,7 +117,7 @@ pub struct AED1 { /// AEAD algorithm. aead: AEADAlgorithm, /// Chunk size. - chunk_size: usize, + chunk_size: u64, /// Initialization vector for the AEAD algorithm. iv: Box<[u8]>, @@ -34,25 +125,16 @@ pub struct AED1 { container: packet::Container, } -impl PartialEq for AED1 { - fn eq(&self, other: &AED1) -> bool { - self.sym_algo == other.sym_algo - && self.aead == other.aead - && self.chunk_size == other.chunk_size - && self.iv == other.iv - && self.container == other.container +impl std::ops::Deref for AED1 { + type Target = packet::Container; + fn deref(&self) -> &Self::Target { + &self.container } } -impl Eq for AED1 {} - -impl std::hash::Hash for AED1 { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - std::hash::Hash::hash(&self.sym_algo, state); - std::hash::Hash::hash(&self.aead, state); - std::hash::Hash::hash(&self.chunk_size, state); - std::hash::Hash::hash(&self.iv, state); - std::hash::Hash::hash(&self.container, state); +impl std::ops::DerefMut for AED1 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.container } } @@ -60,7 +142,7 @@ impl AED1 { /// Creates a new AED1 object. pub fn new(sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, - chunk_size: usize, + chunk_size: u64, iv: Box<[u8]>) -> Result<Self> { if chunk_size.count_ones() != 1 { return Err(Error::InvalidArgument( @@ -76,10 +158,10 @@ impl AED1 { Ok(AED1 { common: Default::default(), - sym_algo: sym_algo, - aead: aead, - chunk_size: chunk_size, - iv: iv, + sym_algo, + aead, + chunk_size, + iv, container: Default::default(), }) } @@ -89,7 +171,7 @@ impl AED1 { self.sym_algo } - /// Sets the sym_algo algorithm. + /// Sets the symmetric algorithm. pub fn set_symmetric_algo(&mut self, sym_algo: SymmetricAlgorithm) -> SymmetricAlgorithm { ::std::mem::replace(&mut self.sym_algo, sym_algo) @@ -106,12 +188,12 @@ impl AED1 { } /// Gets the chunk size. - pub fn chunk_size(&self) -> usize { + pub fn chunk_size(&self) -> u64 { self.chunk_size } - /// Gets the chunk size. - pub fn set_chunk_size(&mut self, chunk_size: usize) -> Result<()> { + /// Sets the chunk size. + pub fn set_chunk_size(&mut self, chunk_size: u64) -> Result<()> { if chunk_size.count_ones() != 1 { return Err(Error::InvalidArgument( format!("chunk size is not a power of two: {}", chunk_size)) @@ -128,9 +210,9 @@ impl AED1 { Ok(()) } - /// Gets the size of a chunk with digest. - pub fn chunk_digest_size(&self) -> Result<usize> { - Ok(self.chunk_size + self.aead.digest_size()?) + /// Gets the size of a chunk with a digest. + pub fn chunk_digest_size(&self) -> Result<u64> { + Ok(self.chunk_size + self.aead.digest_size()? as u64) } /// Gets the initialization vector for the AEAD algorithm. @@ -144,8 +226,6 @@ impl AED1 { } } -impl_container_forwards!(AED1); - impl From<AED1> for Packet { fn from(p: AED1) -> Self { super::AED::from(p).into() @@ -157,19 +237,3 @@ impl From<AED1> for super::AED { super::AED::V1(p) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deref() { - let mut s = AED1::new(SymmetricAlgorithm::AES128, - AEADAlgorithm::EAX, - 64, - vec![].into_boxed_slice()).unwrap(); - assert_eq!(s.body(), &[]); - s.set_body(vec![0, 1, 2]); - assert_eq!(s.body(), &[0, 1, 2]); - } -} |