summaryrefslogtreecommitdiffstats
path: root/openpgp/src/packet/aed.rs
diff options
context:
space:
mode:
Diffstat (limited to 'openpgp/src/packet/aed.rs')
-rw-r--r--openpgp/src/packet/aed.rs166
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]);
- }
-}