1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
//! 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,
    SymmetricAlgorithm,
};
use crate::packet;
use crate::Packet;
use crate::Error;
use crate::Result;

/// Holds an AEAD encrypted data packet.
///
/// 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
///
/// 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,
    /// Symmetric algorithm.
    sym_algo: SymmetricAlgorithm,
    /// AEAD algorithm.
    aead: AEADAlgorithm,
    /// Chunk size.
    chunk_size: u64,
    /// Initialization vector for the AEAD algorithm.
    iv: Box<[u8]>,

    /// This is a container packet.
    container: packet::Container,
}

impl std::ops::Deref for AED1 {
    type Target = packet::Container;
    fn deref(&self) -> &Self::Target {
        &self.container
    }
}

impl std::ops::DerefMut for AED1 {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.container
    }
}

impl AED1 {
    /// Creates a new AED1 object.
    pub fn new(sym_algo: SymmetricAlgorithm,
               aead: AEADAlgorithm,
               chunk_size: u64,
               iv: Box<[u8]>) -> Result<Self> {
        if chunk_size.count_ones() != 1 {
            return Err(Error::InvalidArgument(
                format!("chunk size is not a power of two: {}", chunk_size))
                .into());
        }

        if chunk_size < 64 {
            return Err(Error::InvalidArgument(
                format!("chunk size is too small: {}", chunk_size))
                .into());
        }

        Ok(AED1 {
            common: Default::default(),
            sym_algo,
            aead,
            chunk_size,
            iv,
            container: Default::default(),
        })
    }

    /// Gets the symmetric algorithm.
    pub fn symmetric_algo(&self) -> SymmetricAlgorithm {
        self.sym_algo
    }

    /// Sets the symmetric algorithm.
    pub fn set_symmetric_algo(&mut self, sym_algo: SymmetricAlgorithm)
                              -> SymmetricAlgorithm {
        ::std::mem::replace(&mut self.sym_algo, sym_algo)
    }

    /// Gets the AEAD algorithm.
    pub fn aead(&self) -> AEADAlgorithm {
        self.aead
    }

    /// Sets the AEAD algorithm.
    pub fn set_aead(&mut self, aead: AEADAlgorithm) -> AEADAlgorithm {
        ::std::mem::replace(&mut self.aead, aead)
    }

    /// Gets the chunk size.
    pub fn chunk_size(&self) -> u64 {
        self.chunk_size
    }

    /// 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))
                .into());
        }

        if chunk_size < 64 {
            return Err(Error::InvalidArgument(
                format!("chunk size is too small: {}", chunk_size))
                .into());
        }

        self.chunk_size = chunk_size;
        Ok(())
    }

    /// 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.
    pub fn iv(&self) -> &[u8] {
        &self.iv
    }

    /// Sets the initialization vector for the AEAD algorithm.
    pub fn set_iv(&mut self, iv: Box<[u8]>) -> Box<[u8]> {
        ::std::mem::replace(&mut self.iv, iv)
    }
}

impl From<AED1> for Packet {
    fn from(p: AED1) -> Self {
        super::AED::from(p).into()
    }
}

impl From<AED1> for super::AED {
    fn from(p: AED1) -> Self {
        super::AED::V1(p)
    }
}