summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2018-10-09 17:50:22 +0200
committerJustus Winter <justus@sequoia-pgp.org>2018-10-11 16:27:23 +0200
commit12da5b662d965abeed98b4f1b0f74b6ab887cefe (patch)
tree8365e143ba859f175c27405449a69eda3499f65a
parent99fbdcf9d73a469638e5d035e58e841451ff3579 (diff)
openpgp: Add support for the AEAD encrypted data packet.
- This adds a new packet type, and enough infrastructure to decrypt messages encrypted using AEAD.
-rw-r--r--openpgp/src/aead.rs427
-rw-r--r--openpgp/src/lib.rs4
-rw-r--r--openpgp/src/message/grammar.lalrpop14
-rw-r--r--openpgp/src/message/lexer.rs2
-rw-r--r--openpgp/src/message/mod.rs34
-rw-r--r--openpgp/src/packet/aed.rs167
-rw-r--r--openpgp/src/packet/mod.rs5
-rw-r--r--openpgp/src/parse/parse.rs260
-rw-r--r--openpgp/src/parse/stream.rs2
-rw-r--r--openpgp/src/serialize/mod.rs32
-rw-r--r--openpgp/tests/data/messages/aed/msg-aes128-eax-chunk-size-134217728-password-123.pgpbin0 -> 5314 bytes
-rw-r--r--openpgp/tests/data/messages/aed/msg-aes128-eax-chunk-size-64-password-123.pgpbin0 -> 6611 bytes
-rw-r--r--tool/src/commands/dump.rs9
13 files changed, 870 insertions, 86 deletions
diff --git a/openpgp/src/aead.rs b/openpgp/src/aead.rs
index 0058d4b2..83bd0830 100644
--- a/openpgp/src/aead.rs
+++ b/openpgp/src/aead.rs
@@ -1,11 +1,21 @@
+use std::cmp;
+use std::fmt;
+use std::io::{self, Read};
+
use nettle::{aead, cipher};
+use buffered_reader::BufferedReader;
+use buffered_reader::BufferedReaderGeneric;
-use Error;
-use Result;
use constants::{
AEADAlgorithm,
SymmetricAlgorithm,
};
+use conversions::{
+ write_be_u64,
+};
+use Error;
+use Result;
+use SessionKey;
impl AEADAlgorithm {
/// Returns the digest size of the AEAD algorithm.
@@ -63,3 +73,416 @@ impl AEADAlgorithm {
}
}
}
+
+const AD_PREFIX_LEN: usize = 5;
+
+/// A `Read`er for decrypting AEAD-encrypted data.
+pub struct Decryptor<R: io::Read> {
+ // The encrypted data.
+ source: R,
+
+ cipher: SymmetricAlgorithm,
+ aead: AEADAlgorithm,
+ key: SessionKey,
+ iv: Box<[u8]>,
+ ad: [u8; AD_PREFIX_LEN + 8 + 8],
+
+ digest_size: usize,
+ chunk_size: usize,
+ chunk_index: u64,
+ bytes_decrypted: u64,
+ // Up to a chunk of unread data.
+ buffer: Vec<u8>,
+}
+
+impl<R: io::Read> Decryptor<R> {
+ /// Instantiate a new AEAD decryptor.
+ ///
+ /// `source` is the source to wrap.
+ pub fn new(version: u8, cipher: SymmetricAlgorithm, aead: AEADAlgorithm,
+ chunk_size: usize, iv: &[u8], key: &SessionKey, source: R)
+ -> Result<Self> {
+ Ok(Decryptor {
+ source: source,
+ cipher: cipher,
+ aead: aead,
+ key: key.clone(),
+ iv: Vec::from(iv).into_boxed_slice(),
+ ad: [
+ // Prefix.
+ 0xd4, version, cipher.into(), aead.into(),
+ chunk_size.trailing_zeros() as u8 - 6,
+ // Chunk index.
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ // Message size.
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ ],
+ digest_size: aead.digest_size()?,
+ chunk_size: chunk_size,
+ chunk_index: 0,
+ bytes_decrypted: 0,
+ buffer: Vec::with_capacity(chunk_size),
+ })
+ }
+
+ fn hash_associated_data(&mut self, aead: &mut Box<aead::Aead>,
+ final_digest: bool) {
+ // Prepare the associated data.
+ write_be_u64(&mut self.ad[AD_PREFIX_LEN..AD_PREFIX_LEN + 8],
+ self.chunk_index);
+
+ if final_digest {
+ write_be_u64(&mut self.ad[AD_PREFIX_LEN + 8..],
+ self.bytes_decrypted);
+ aead.update(&self.ad);
+ } else {
+ aead.update(&self.ad[..AD_PREFIX_LEN + 8]);
+ }
+ }
+
+ fn make_aead(&mut self) -> Result<Box<aead::Aead>> {
+ // The chunk index is XORed into the IV.
+ let mut chunk_index_be64 = vec![0u8; 8];
+ write_be_u64(&mut chunk_index_be64, self.chunk_index);
+
+ match self.aead {
+ AEADAlgorithm::EAX => {
+ // The nonce for EAX mode is computed by treating the
+ // starting initialization vector as a 16-octet,
+ // big-endian value and exclusive-oring the low eight
+ // octets of it with the chunk index.
+ let iv_len = self.iv.len();
+ for (i, o) in &mut self.iv[iv_len - 8..].iter_mut()
+ .enumerate()
+ {
+ // The lower eight octets of the associated data
+ // are the big endian representation of the chunk
+ // index.
+ *o ^= chunk_index_be64[i];
+ }
+
+ // Instantiate the AEAD cipher.
+ let aead = self.aead.context(self.cipher, &self.key, &self.iv)?;
+
+ // Restore the IV.
+ for (i, o) in &mut self.iv[iv_len - 8..].iter_mut()
+ .enumerate()
+ {
+ *o ^= chunk_index_be64[i];
+ }
+
+ Ok(aead)
+ }
+ _ => Err(Error::UnsupportedAEADAlgorithm(self.aead).into()),
+ }
+ }
+
+ fn read_helper(&mut self, plaintext: &mut [u8]) -> Result<usize> {
+ let mut pos = 0;
+
+ // 1. Copy any buffered data.
+ if self.buffer.len() > 0 {
+ let to_copy = cmp::min(self.buffer.len(), plaintext.len());
+ &plaintext[..to_copy].copy_from_slice(&self.buffer[..to_copy]);
+ self.buffer.drain(..to_copy);
+ pos = to_copy;
+ }
+
+ if pos == plaintext.len() {
+ return Ok(pos);
+ }
+
+ // 2. Decrypt as many whole chunks as `plaintext` can hold.
+ let n_chunks = (plaintext.len() - pos) / self.chunk_size;
+ let chunk_digest_size = self.chunk_size + self.digest_size;
+ let mut to_copy = n_chunks * self.chunk_size;
+ let to_read = n_chunks * chunk_digest_size;
+
+ let mut ciphertext = Vec::new();
+ let result = (&mut self.source).take(to_read as u64)
+ .read_to_end(&mut ciphertext);
+ let short_read;
+ match result {
+ Ok(amount) => {
+ if to_read != 0 && amount == 0 {
+ // Exhausted source.
+ return Ok(pos);
+ }
+
+ short_read = amount < to_copy;
+ to_copy = amount;
+ ciphertext.truncate(to_copy);
+ },
+ // We encountered an error, but we did read some.
+ Err(_) if pos > 0 => return Ok(pos),
+ Err(e) => return Err(e.into()),
+ }
+
+ // Buffer to hold digests.
+ let mut digest = vec![0u8; self.digest_size];
+
+ // At the end of the stream, there is an additional tag. Be
+ // careful not to consume this tag.
+ let ciphertext_end = if short_read {
+ ciphertext.len() - self.digest_size
+ } else {
+ ciphertext.len()
+ };
+
+ for chunk in (&ciphertext[..ciphertext_end]).chunks(chunk_digest_size) {
+ let mut aead = self.make_aead()?;
+
+ // Digest the associated data.
+ self.hash_associated_data(&mut aead, false);
+
+ // Decrypt the chunk.
+ aead.decrypt(
+ &mut plaintext[pos..pos + chunk.len() - self.digest_size],
+ &chunk[..chunk.len() - self.digest_size]);
+ self.bytes_decrypted += (chunk.len() - self.digest_size) as u64;
+
+ // Check digest.
+ aead.digest(&mut digest);
+ if &digest[..] != &chunk[chunk.len() - self.digest_size..] {
+ return Err(Error::ManipulatedMessage.into());
+ }
+
+ // Increase index, update position in plaintext.
+ self.chunk_index += 1;
+ pos += chunk.len() - self.digest_size;
+ }
+
+ if short_read {
+ // We read the whole ciphertext, now check the final digest.
+ let mut aead = self.make_aead()?;
+ self.hash_associated_data(&mut aead, true);
+ let mut nada = [0; 0];
+ aead.decrypt(&mut nada, b"");
+ aead.digest(&mut digest);
+ if &digest[..] != &ciphertext[ciphertext_end..] {
+ return Err(Error::ManipulatedMessage.into());
+ }
+ }
+
+ if short_read || pos == plaintext.len() {
+ return Ok(pos);
+ }
+
+ // 3. The last bit is a partial chunk. Buffer it.
+ let mut to_copy = plaintext.len() - pos;
+ assert!(0 < to_copy);
+ assert!(to_copy < self.chunk_size);
+
+ let mut ciphertext = Vec::new();
+ let result = (&mut self.source).take(chunk_digest_size as u64)
+ .read_to_end(&mut ciphertext);
+ let short_read;
+ match result {
+ Ok(amount) => {
+ if amount == 0 {
+ return Ok(pos);
+ }
+
+ short_read = amount < chunk_digest_size;
+
+ // Make sure `ciphertext` is not larger than the
+ // amount of data that was actually read.
+ ciphertext.truncate(amount);
+
+ // Make sure we don't read more than is available.
+ to_copy = cmp::min(to_copy,
+ ciphertext.len() - self.digest_size
+ - if short_read {
+ self.digest_size
+ } else {
+ 0
+ });
+ },
+ // We encountered an error, but we did read some.
+ Err(_) if pos > 0 => return Ok(pos),
+ Err(e) => return Err(e.into()),
+ }
+ assert!(ciphertext.len() <= self.chunk_size + self.digest_size);
+
+ let mut aead = self.make_aead()?;
+
+ // Digest the associated data.
+ self.hash_associated_data(&mut aead, false);
+
+ // At the end of the stream, there is an additional tag. Be
+ // careful not to consume this tag.
+ let ciphertext_end = if short_read {
+ ciphertext.len() - self.digest_size
+ } else {
+ ciphertext.len()
+ };
+
+ while self.buffer.len() < ciphertext_end - self.digest_size {
+ self.buffer.push(0u8);
+ }
+ self.buffer.truncate(ciphertext_end - self.digest_size);
+
+ // Decrypt the chunk.
+ aead.decrypt(&mut self.buffer,
+ &ciphertext[..ciphertext_end - self.digest_size]);
+ self.bytes_decrypted += (ciphertext_end - self.digest_size) as u64;
+
+ // Check digest.
+ aead.digest(&mut digest);
+ if &digest[..]
+ != &ciphertext[ciphertext_end - self.digest_size..ciphertext_end] {
+ return Err(Error::ManipulatedMessage.into());
+ }
+
+ // Increase index.
+ self.chunk_index += 1;
+
+ &plaintext[pos..pos + to_copy].copy_from_slice(&self.buffer[..to_copy]);
+ self.buffer.drain(..to_copy);
+ pos += to_copy;
+
+ if short_read {
+ // We read the whole ciphertext, now check the final digest.
+ let mut aead = self.make_aead()?;
+ self.hash_associated_data(&mut aead, true);
+ let mut nada = [0; 0];
+ aead.decrypt(&mut nada, b"");
+ aead.digest(&mut digest);
+ if &digest[..] != &ciphertext[ciphertext_end..] {
+ return Err(Error::ManipulatedMessage.into());
+ }
+ }
+
+ Ok(pos)
+ }
+}
+
+// Note: this implementation tries *very* hard to make sure we don't
+// gratuitiously do a short read. Specifically, if the return value
+// is less than `plaintext.len()`, then it is either because we
+// reached the end of the input or an error occured.
+impl<R: io::Read> io::Read for Decryptor<R> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ match self.read_helper(buf) {
+ Ok(n) => Ok(n),
+ Err(e) => match e.downcast::<io::Error>() {
+ // An io::Error. Pass as-is.
+ Ok(e) => Err(e),
+ // A failure. Create a compat object and wrap it.
+ Err(e) => Err(io::Error::new(io::ErrorKind::Other,
+ e.compat())),
+ },
+ }
+ }
+}
+
+/// A `BufferedReader` that decrypts AEAD-encrypted data as it is
+/// read.
+pub(crate) struct BufferedReaderDecryptor<R: BufferedReader<C>, C> {
+ reader: BufferedReaderGeneric<Decryptor<R>, C>,
+}
+
+impl <R: BufferedReader<C>, C> BufferedReaderDecryptor<R, C> {
+ /// Like `new()`, but sets a cookie, which can be retrieved using
+ /// the `cookie_ref` and `cookie_mut` methods, and set using
+ /// the `cookie_set` method.
+ pub fn with_cookie(version: u8, cipher: SymmetricAlgorithm,
+ aead: AEADAlgorithm, chunk_size: usize, iv: &[u8],
+ key: &SessionKey, source: R, cookie: C)
+ -> Result<Self>
+ {
+ Ok(BufferedReaderDecryptor {
+ reader: BufferedReaderGeneric::with_cookie(
+ Decryptor::new(version, cipher, aead, chunk_size, iv, key,
+ source)?,
+ None, cookie),
+ })
+ }
+}
+
+impl<R: BufferedReader<C>, C> io::Read for BufferedReaderDecryptor<R, C> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.reader.read(buf)
+ }
+}
+
+impl <R: BufferedReader<C>, C> fmt::Debug for BufferedReaderDecryptor<R, C> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("BufferedReaderDecryptor")
+ .field("reader", &self.get_ref().unwrap())
+ .finish()
+ }
+}
+
+impl<R: BufferedReader<C>, C> BufferedReader<C>
+ for BufferedReaderDecryptor<R, C> {
+ fn buffer(&self) -> &[u8] {
+ return self.reader.buffer();
+ }
+
+ fn data(&mut self, amount: usize) -> io::Result<&[u8]> {
+ return self.reader.data(amount);
+ }
+
+ fn data_hard(&mut self, amount: usize) -> io::Result<&[u8]> {
+ return self.reader.data_hard(amount);
+ }
+
+ fn data_eof(&mut self) -> io::Result<&[u8]> {
+ return self.reader.data_eof();
+ }
+
+ fn consume(&mut self, amount: usize) -> &[u8] {
+ return self.reader.consume(amount);
+ }
+
+ fn data_consume(&mut self, amount: usize)
+ -> io::Result<&[u8]> {
+ return self.reader.data_consume(amount);
+ }
+
+ fn data_consume_hard(&mut self, amount: usize) -> io::Result<&[u8]> {
+ return self.reader.data_consume_hard(amount);
+ }
+
+ fn read_be_u16(&mut self) -> io::Result<u16> {
+ return self.reader.read_be_u16();
+ }
+
+ fn read_be_u32(&mut self) -> io::Result<u32> {
+ return self.reader.read_be_u32();
+ }
+
+ fn steal(&mut self, amount: usize) -> io::Result<Vec<u8>> {
+ return self.reader.steal(amount);
+ }
+
+ fn steal_eof(&mut self) -> io::Result<Vec<u8>> {
+ return self.reader.steal_eof();
+ }
+
+ fn get_mut(&mut self) -> Option<&mut BufferedReader<C>> {
+ Some(&mut self.reader.reader.source)
+ }
+
+ fn get_ref(&self) -> Option<&BufferedReader<C>> {
+ Some(&self.reader.reader.source)
+ }
+
+ fn into_inner<'b>(self: Box<Self>)
+ -> Option<Box<BufferedReader<C> + 'b>> where Self: 'b {
+ Some(Box::new(self.reader.reader.source))
+ }
+
+ fn cookie_set(&mut self, cookie: C) -> C {
+ self.reader.cookie_set(cookie)
+ }
+
+ fn cookie_ref(&self) -> &C {
+ self.reader.cookie_ref()
+ }
+
+ fn cookie_mut(&mut self) -> &mut C {
+ self.reader.cookie_mut()
+ }
+}
diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs
index 6316448b..22843a57 100644
--- a/openpgp/src/lib.rs
+++ b/openpgp/src/lib.rs
@@ -311,6 +311,8 @@ pub enum Packet {
SEIP(seip::SEIP),
/// Modification detection code packet.
MDC(mdc::MDC),
+ /// AEAD Encrypted Data Packet.
+ AED(packet::AED),
}
impl Packet {
@@ -336,6 +338,7 @@ impl Packet {
&Packet::SKESK(_) => Tag::SKESK,
&Packet::SEIP(_) => Tag::SEIP,
&Packet::MDC(_) => Tag::MDC,
+ &Packet::AED(_) => Tag::AED,
}
}
@@ -363,6 +366,7 @@ impl Packet {
&Packet::SKESK(_) => Some(Tag::SKESK),
&Packet::SEIP(_) => Some(Tag::SEIP),
&Packet::MDC(_) => Some(Tag::MDC),
+ &Packet::AED(_) => Some(Tag::AED),
}
}
}
diff --git a/openpgp/src/message/grammar.lalrpop b/openpgp/src/message/grammar.lalrpop
index 1fe2c2c7..d5c0ea36 100644
--- a/openpgp/src/message/grammar.lalrpop
+++ b/openpgp/src/message/grammar.lalrpop
@@ -21,10 +21,19 @@ SeipPart: () = {
SEIP OPAQUE_CONTENT POP,
}
-// An encrypted part is 0 or more ESKs followed by a SEIP packet.
+AedPart: () = {
+ AED Message POP,
+}
+
+// An encrypted part is 0 or more ESKs followed by an encryption container.
EncryptedPart: () = {
+ EncryptionContainer,
+ ESKS EncryptionContainer,
+};
+
+EncryptionContainer: () = {
SeipPart,
- ESKS SeipPart,
+ AedPart,
};
ESKS: () = {
@@ -61,6 +70,7 @@ extern {
PKESK => lexer::Token::PKESK,
SEIP => lexer::Token::SEIP,
MDC => lexer::Token::MDC,
+ AED => lexer::Token::AED,
OPS => lexer::Token::OPS,
SIG => lexer::Token::SIG,
POP => lexer::Token::Pop,
diff --git a/openpgp/src/message/lexer.rs b/openpgp/src/message/lexer.rs
index 778735d3..fc561d62 100644
--- a/openpgp/src/message/lexer.rs
+++ b/openpgp/src/message/lexer.rs
@@ -23,6 +23,8 @@ pub enum Token {
SEIP,
/// An MDC packet.
MDC,
+ /// An AED packet.
+ AED,
/// A OnePassSig packet.
OPS,
diff --git a/openpgp/src/message/mod.rs b/openpgp/src/message/mod.rs
index 557c94e0..dfa332a9 100644
--- a/openpgp/src/message/mod.rs
+++ b/openpgp/src/message/mod.rs
@@ -209,6 +209,7 @@ impl MessageValidator {
Tag::PKESK => Token::PKESK,
Tag::SEIP => Token::SEIP,
Tag::MDC => Token::MDC,
+ Tag::AED => Token::AED,
Tag::OnePassSig => Token::OPS,
Tag::Signature => Token::SIG,
_ => {
@@ -307,7 +308,8 @@ impl Message {
v.push(packet.tag(), path.len() as isize - 1);
match packet {
- Packet::CompressedData(_) | Packet::SEIP(_) => {
+ Packet::CompressedData(_) | Packet::SEIP(_) | Packet::AED(_) =>
+ {
// If a container's content is not unpacked, then
// we treat the content as an opaque message.
@@ -467,6 +469,36 @@ mod tests {
},
TestVector {
+ s: &[AED, Literal, Pop],
+ result: true,
+ },
+ TestVector {
+ s: &[CompressedData, AED, Literal, Pop, Pop],
+ result: true,
+ },
+ TestVector {
+ s: &[CompressedData, AED, CompressedData, Literal,
+ Pop, Pop, Pop],
+ result: true,
+ },
+ TestVector {
+ s: &[AED, Pop],
+ result: false,
+ },
+ TestVector {
+ s: &[SKESK, AED, Literal, Pop],
+ result: true,
+ },
+ TestVector {
+ s: &[PKESK, AED, Literal, Pop],
+ result: true,
+ },
+ TestVector {
+ s: &[SKESK, SKESK, AED, Literal, Pop],
+ result: true,
+ },
+
+ TestVector {
s: &[OPS, Literal, SIG],
result: true,
},
diff --git a/openpgp/src/packet/aed.rs b/openpgp/src/packet/aed.rs
new file mode 100644
index 00000000..8edc4dc2
--- /dev/null
+++ b/openpgp/src/packet/aed.rs
@@ -0,0 +1,167 @@
+use std::ops::{Deref, DerefMut};
+
+use constants::{
+ AEADAlgorithm,
+ SymmetricAlgorithm,
+};
+use packet::{self, Common};
+use Packet;
+use Error;
+use Result;
+
+/// Holds an AEAD encrypted data packet.
+///
+/// An AEAD encrypted data packet is a container. See [Section 5.16
+/// of RFC 4880bis] for details.
+///
+/// [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.16
+#[derive(PartialEq, Eq, Hash, Clone, Debug)]
+pub struct AED {
+ /// CTB packet header fields.
+ pub(crate) common: packet::Common,
+ /// AED version. Must be 1.
+ version: u8,
+ /// Cipher algorithm.
+ cipher: SymmetricAlgorithm,
+ /// AEAD algorithm.
+ aead: AEADAlgorithm,
+ /// Chunk size.
+ chunk_size: usize,
+ /// Initialization vector for the AEAD algorithm.
+ iv: Box<[u8]>,
+}
+
+impl AED {
+ /// Creates a new AED object.
+ pub fn new(cipher: SymmetricAlgorithm,
+ aead: AEADAlgorithm,
+ chunk_size: usize,
+ 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(AED {
+ common: Default::default(),
+ version: 1,
+ cipher: cipher,
+ aead: aead,
+ chunk_size: chunk_size,
+ iv: iv,
+ })
+ }
+
+ /// Gets the version.
+ pub fn version(&self) -> u8 {
+ self.version
+ }
+
+ /// Gets the cipher algorithm.
+ pub fn cipher(&self) -> SymmetricAlgorithm {
+ self.cipher
+ }
+
+ /// Sets the cipher algorithm.
+ pub fn set_cipher(&mut self, cipher: SymmetricAlgorithm) {
+ self.cipher = cipher;
+ }
+
+ /// Gets the AEAD algorithm.
+ pub fn aead(&self) -> AEADAlgorithm {
+ self.aead
+ }
+
+ /// Sets the AEAD algorithm.
+ pub fn set_aead(&mut self, aead: AEADAlgorithm) {
+ self.aead = aead;
+ }
+
+ /// Gets the chunk size.
+ pub fn chunk_size(&self) -> usize {
+ self.chunk_size
+ }
+
+ /// Gets the chunk size.
+ pub fn set_chunk_size(&mut self, chunk_size: usize) -> 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 digest.
+ pub fn chunk_digest_size(&self) -> Result<usize> {
+ Ok(self.chunk_size + self.aead.digest_size()?)
+ }
+
+ /// 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]>) {
+ self.iv = iv;
+ }
+
+ /// Convert the `AED` struct to a `Packet`.
+ pub fn to_packet(self) -> Packet {
+ Packet::AED(self)
+ }
+}
+
+impl From<AED> for Packet {
+ fn from(s: AED) -> Self {
+ s.to_packet()
+ }
+}
+
+// Allow transparent access of common fields.
+impl<'a> Deref for AED {
+ type Target = Common;
+
+ fn deref(&self) -> &Self::Target {
+ &self.common
+ }
+}
+
+// Allow transparent access of common fields.
+impl<'a> DerefMut for AED {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.common
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn deref() {
+ let mut s = AED::new(SymmetricAlgorithm::AES128,
+ AEADAlgorithm::EAX,
+ 64,
+ vec![].into_boxed_slice()).unwrap();
+ assert_eq!(s.body(), None);
+