1 //! AEAD encrypted data packets. 2 //! 3 //! An encryption container using [Authenticated Encryption with 4 //! Additional Data]. 5 //! 6 //! The AED packet is a new packet specified in [Section 5.16 of RFC 7 //! 4880bis]. Its aim is to replace the [SEIP packet], whose security 8 //! has been partially compromised. SEIP's weaknesses includes its 9 //! use of CFB mode (e.g., EFAIL-style CFB gadgets, see Section 5.3 of 10 //! the [EFAIL paper]), its use of [SHA-1] for integrity protection, and 11 //! the ability to [downgrade SEIP packets] to much weaker SED 12 //! packets. 13 //! 14 //! Although the decision to use AEAD is uncontroversial, the design 15 //! specified in RFC 4880bis is. According to [RFC 5116], decrypted 16 //! AEAD data can only be released for processing after its 17 //! authenticity has been checked: 18 //! 19 //! > [The authenticated decryption operation] has only a single 20 //! > output, either a plaintext value P or a special symbol FAIL that 21 //! > indicates that the inputs are not authentic. 22 //! 23 //! The controversy has to do with streaming, which OpenPGP has 24 //! traditionally supported. Streaming a message means that the 25 //! amount of data that needs to be buffered when processing a message 26 //! is independent of the message's length. 27 //! 28 //! At first glance, the AEAD mechanism in RFC 4880bis appears to 29 //! support this mode of operation: instead of encrypting the whole 30 //! message using AEAD, which would require buffering all of the 31 //! plaintext when decrypting the message, the message is chunked, the 32 //! individual chunks are linked together, and AEAD is used to encrypt 33 //! and protect each individual chunk. Because the plaintext from an 34 //! individual chunk can be integrity checked, an implementation only 35 //! needs to buffer a chunk worth of data. 36 //! 37 //! Unfortunately, RFC 4880bis allows chunk sizes that are, in 38 //! practice, unbounded. Specifically, a chunk can be up to 4 39 //! exbibytes in size. Thus when encountering messages that can't be 40 //! buffered, an OpenPGP implementation has a choice: it can either 41 //! release data that has not been integrity checked and violate RFC 42 //! 5116, or it can fail to process the message. As of 2020, [GnuPG] 43 //! and [RNP] process unauthenticated plaintext. From a user 44 //! perspective, it then appears that implementations that choose to 45 //! follow RFC 5116 are impaired: "GnuPG can decrypt it," they think, 46 //! "why can't Sequoia?" This creates pressure on other 47 //! implementations to also behave insecurely. 48 //! 49 //! [Werner argues] that AEAD is not about authenticating the data. 50 //! That is the purpose of the signature. The reason to introduce 51 //! AEAD is to get the benefits of more modern cryptography, and to be 52 //! able to more quickly detect rare transmission errors. Our 53 //! position is that an integrity check provides real protection: it 54 //! can detect modified ciphertext. And, if we are going to stream, 55 //! then this protection is essential as it protects the user from 56 //! real, demonstrated attacks like [EFAIL]. 57 //! 58 //! RFC 4880bis has not been finalized. So, it is still possible that 59 //! the AEAD mechanism will change (which is why the AED packet is 60 //! marked as experimental). Despite our concerns, because other 61 //! OpenPGP implementations already emit the AEAD packet, we provide 62 //! *experimental* support for it in Sequoia. 63 //! 64 //! [Authenticated Encryption with Additional Data]: https://en.wikipedia.org/wiki/Authenticated_encryption 65 //! [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-5.16 66 //! [EFAIL paper]: https://www.usenix.org/conference/usenixsecurity18/presentation/poddebniak 67 //! [SHA-1]: https://sha-mbles.github.io/ 68 //! [SEIP packet]: https://tools.ietf.org/html/rfc4880#section-5.13 69 //! [RFC 5116]: https://tools.ietf.org/html/rfc5116#section-2.2 70 //! [downgrade SEIP packets]: https://mailarchive.ietf.org/arch/msg/openpgp/JLn7sL6TqikUf-cD34lN7kof7_A/ 71 //! [GnuPG]: https://mailarchive.ietf.org/arch/msg/openpgp/fmQgRm94jhvPLEOi0J-o7A8LpkY/ 72 //! [RNP]: https://github.com/rnpgp/rnp/issues/807 73 //! [Werner argues]: https://mailarchive.ietf.org/arch/msg/openpgp/J428Mqq3-pHTU4C76EgP5sPkvtA 74 //! [EFAIL]: https://efail.de/ 75 76 use crate::types::{ 77 AEADAlgorithm, 78 SymmetricAlgorithm, 79 }; 80 use crate::packet; 81 use crate::Packet; 82 use crate::Error; 83 use crate::Result; 84 85 /// Holds an AEAD encrypted data packet. 86 /// 87 /// An AEAD encrypted data packet holds encrypted data. The data 88 /// contains additional OpenPGP packets. See [Section 5.16 of RFC 89 /// 4880bis] for details. 90 /// 91 /// An AED packet is not normally instantiated directly. In most 92 /// cases, you'll create one as a side-effect of encrypting a message 93 /// using the [streaming serializer], or parsing an encrypted message 94 /// using the [`PacketParser`]. 95 /// 96 /// This feature is 97 /// [experimental](../../index.html#experimental-features). It has 98 /// not been standardized and we advise users to not emit AED packets. 99 /// 100 /// [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.16 101 /// [streaming serializer]: ../serialize/stream/index.html 102 /// [`PacketParser`]: ../parse/index.html 103 /// 104 /// # A note on equality 105 /// 106 /// An unprocessed (encrypted) `AED` packet is never considered equal 107 /// to a processed (decrypted) one. Likewise, a processed (decrypted) 108 /// packet is never considered equal to a structured (parsed) one. 109 // IMPORTANT: If you add fields to this struct, you need to explicitly 110 // IMPORTANT: implement PartialEq, Eq, and Hash. 111 #[derive(Clone, Debug, PartialEq, Eq, Hash)] 112 pub struct AED1 { 113 /// CTB packet header fields. 114 pub(crate) common: packet::Common, 115 /// Symmetric algorithm. 116 sym_algo: SymmetricAlgorithm, 117 /// AEAD algorithm. 118 aead: AEADAlgorithm, 119 /// Chunk size. 120 chunk_size: u64, 121 /// Initialization vector for the AEAD algorithm. 122 iv: Box<[u8]>, 123 124 /// This is a container packet. 125 container: packet::Container, 126 } 127 128 impl std::ops::Deref for AED1 { 129 type Target = packet::Container; deref(&self) -> &Self::Target130 fn deref(&self) -> &Self::Target { 131 &self.container 132 } 133 } 134 135 impl std::ops::DerefMut for AED1 { deref_mut(&mut self) -> &mut Self::Target136 fn deref_mut(&mut self) -> &mut Self::Target { 137 &mut self.container 138 } 139 } 140 141 impl AED1 { 142 /// Creates a new AED1 object. new(sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: u64, iv: Box<[u8]>) -> Result<Self>143 pub fn new(sym_algo: SymmetricAlgorithm, 144 aead: AEADAlgorithm, 145 chunk_size: u64, 146 iv: Box<[u8]>) -> Result<Self> { 147 if chunk_size.count_ones() != 1 { 148 return Err(Error::InvalidArgument( 149 format!("chunk size is not a power of two: {}", chunk_size)) 150 .into()); 151 } 152 153 if chunk_size < 64 { 154 return Err(Error::InvalidArgument( 155 format!("chunk size is too small: {}", chunk_size)) 156 .into()); 157 } 158 159 Ok(AED1 { 160 common: Default::default(), 161 sym_algo, 162 aead, 163 chunk_size, 164 iv, 165 container: Default::default(), 166 }) 167 } 168 169 /// Gets the symmetric algorithm. symmetric_algo(&self) -> SymmetricAlgorithm170 pub fn symmetric_algo(&self) -> SymmetricAlgorithm { 171 self.sym_algo 172 } 173 174 /// Sets the symmetric algorithm. set_symmetric_algo(&mut self, sym_algo: SymmetricAlgorithm) -> SymmetricAlgorithm175 pub fn set_symmetric_algo(&mut self, sym_algo: SymmetricAlgorithm) 176 -> SymmetricAlgorithm { 177 ::std::mem::replace(&mut self.sym_algo, sym_algo) 178 } 179 180 /// Gets the AEAD algorithm. aead(&self) -> AEADAlgorithm181 pub fn aead(&self) -> AEADAlgorithm { 182 self.aead 183 } 184 185 /// Sets the AEAD algorithm. set_aead(&mut self, aead: AEADAlgorithm) -> AEADAlgorithm186 pub fn set_aead(&mut self, aead: AEADAlgorithm) -> AEADAlgorithm { 187 ::std::mem::replace(&mut self.aead, aead) 188 } 189 190 /// Gets the chunk size. chunk_size(&self) -> u64191 pub fn chunk_size(&self) -> u64 { 192 self.chunk_size 193 } 194 195 /// Sets the chunk size. set_chunk_size(&mut self, chunk_size: u64) -> Result<()>196 pub fn set_chunk_size(&mut self, chunk_size: u64) -> Result<()> { 197 if chunk_size.count_ones() != 1 { 198 return Err(Error::InvalidArgument( 199 format!("chunk size is not a power of two: {}", chunk_size)) 200 .into()); 201 } 202 203 if chunk_size < 64 { 204 return Err(Error::InvalidArgument( 205 format!("chunk size is too small: {}", chunk_size)) 206 .into()); 207 } 208 209 self.chunk_size = chunk_size; 210 Ok(()) 211 } 212 213 /// Gets the size of a chunk with a digest. chunk_digest_size(&self) -> Result<u64>214 pub fn chunk_digest_size(&self) -> Result<u64> { 215 Ok(self.chunk_size + self.aead.digest_size()? as u64) 216 } 217 218 /// Gets the initialization vector for the AEAD algorithm. iv(&self) -> &[u8]219 pub fn iv(&self) -> &[u8] { 220 &self.iv 221 } 222 223 /// Sets the initialization vector for the AEAD algorithm. set_iv(&mut self, iv: Box<[u8]>) -> Box<[u8]>224 pub fn set_iv(&mut self, iv: Box<[u8]>) -> Box<[u8]> { 225 ::std::mem::replace(&mut self.iv, iv) 226 } 227 } 228 229 impl From<AED1> for Packet { from(p: AED1) -> Self230 fn from(p: AED1) -> Self { 231 super::AED::from(p).into() 232 } 233 } 234 235 impl From<AED1> for super::AED { from(p: AED1) -> Self236 fn from(p: AED1) -> Self { 237 super::AED::V1(p) 238 } 239 } 240