summaryrefslogtreecommitdiffstats
path: root/third_party/rust/zip/src/aes.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/zip/src/aes.rs')
-rw-r--r--third_party/rust/zip/src/aes.rs185
1 files changed, 185 insertions, 0 deletions
diff --git a/third_party/rust/zip/src/aes.rs b/third_party/rust/zip/src/aes.rs
new file mode 100644
index 0000000000..8997705c69
--- /dev/null
+++ b/third_party/rust/zip/src/aes.rs
@@ -0,0 +1,185 @@
+//! Implementation of the AES decryption for zip files.
+//!
+//! This was implemented according to the [WinZip specification](https://www.winzip.com/win/en/aes_info.html).
+//! Note that using CRC with AES depends on the used encryption specification, AE-1 or AE-2.
+//! If the file is marked as encrypted with AE-2 the CRC field is ignored, even if it isn't set to 0.
+
+use crate::aes_ctr;
+use crate::types::AesMode;
+use constant_time_eq::constant_time_eq;
+use hmac::{Hmac, Mac};
+use sha1::Sha1;
+use std::io::{self, Read};
+
+/// The length of the password verifcation value in bytes
+const PWD_VERIFY_LENGTH: usize = 2;
+/// The length of the authentication code in bytes
+const AUTH_CODE_LENGTH: usize = 10;
+/// The number of iterations used with PBKDF2
+const ITERATION_COUNT: u32 = 1000;
+
+/// Create a AesCipher depending on the used `AesMode` and the given `key`.
+///
+/// # Panics
+///
+/// This panics if `key` doesn't have the correct size for the chosen aes mode.
+fn cipher_from_mode(aes_mode: AesMode, key: &[u8]) -> Box<dyn aes_ctr::AesCipher> {
+ match aes_mode {
+ AesMode::Aes128 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes128>::new(key))
+ as Box<dyn aes_ctr::AesCipher>,
+ AesMode::Aes192 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes192>::new(key))
+ as Box<dyn aes_ctr::AesCipher>,
+ AesMode::Aes256 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes256>::new(key))
+ as Box<dyn aes_ctr::AesCipher>,
+ }
+}
+
+// An aes encrypted file starts with a salt, whose length depends on the used aes mode
+// followed by a 2 byte password verification value
+// then the variable length encrypted data
+// and lastly a 10 byte authentication code
+pub struct AesReader<R> {
+ reader: R,
+ aes_mode: AesMode,
+ data_length: u64,
+}
+
+impl<R: Read> AesReader<R> {
+ pub fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> AesReader<R> {
+ let data_length = compressed_size
+ - (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64;
+
+ Self {
+ reader,
+ aes_mode,
+ data_length,
+ }
+ }
+
+ /// Read the AES header bytes and validate the password.
+ ///
+ /// Even if the validation succeeds, there is still a 1 in 65536 chance that an incorrect
+ /// password was provided.
+ /// It isn't possible to check the authentication code in this step. This will be done after
+ /// reading and decrypting the file.
+ ///
+ /// # Returns
+ ///
+ /// If the password verification failed `Ok(None)` will be returned to match the validate
+ /// method of ZipCryptoReader.
+ pub fn validate(mut self, password: &[u8]) -> io::Result<Option<AesReaderValid<R>>> {
+ let salt_length = self.aes_mode.salt_length();
+ let key_length = self.aes_mode.key_length();
+
+ let mut salt = vec![0; salt_length];
+ self.reader.read_exact(&mut salt)?;
+
+ // next are 2 bytes used for password verification
+ let mut pwd_verification_value = vec![0; PWD_VERIFY_LENGTH];
+ self.reader.read_exact(&mut pwd_verification_value)?;
+
+ // derive a key from the password and salt
+ // the length depends on the aes key length
+ let derived_key_len = 2 * key_length + PWD_VERIFY_LENGTH;
+ let mut derived_key: Vec<u8> = vec![0; derived_key_len];
+
+ // use PBKDF2 with HMAC-Sha1 to derive the key
+ pbkdf2::pbkdf2::<Hmac<Sha1>>(password, &salt, ITERATION_COUNT, &mut derived_key);
+ let decrypt_key = &derived_key[0..key_length];
+ let hmac_key = &derived_key[key_length..key_length * 2];
+ let pwd_verify = &derived_key[derived_key_len - 2..];
+
+ // the last 2 bytes should equal the password verification value
+ if pwd_verification_value != pwd_verify {
+ // wrong password
+ return Ok(None);
+ }
+
+ let cipher = cipher_from_mode(self.aes_mode, decrypt_key);
+ let hmac = Hmac::<Sha1>::new_from_slice(hmac_key).unwrap();
+
+ Ok(Some(AesReaderValid {
+ reader: self.reader,
+ data_remaining: self.data_length,
+ cipher,
+ hmac,
+ finalized: false,
+ }))
+ }
+}
+
+/// A reader for aes encrypted files, which has already passed the first password check.
+///
+/// There is a 1 in 65536 chance that an invalid password passes that check.
+/// After the data has been read and decrypted an HMAC will be checked and provide a final means
+/// to check if either the password is invalid or if the data has been changed.
+pub struct AesReaderValid<R: Read> {
+ reader: R,
+ data_remaining: u64,
+ cipher: Box<dyn aes_ctr::AesCipher>,
+ hmac: Hmac<Sha1>,
+ finalized: bool,
+}
+
+impl<R: Read> Read for AesReaderValid<R> {
+ /// This implementation does not fulfill all requirements set in the trait documentation.
+ ///
+ /// ```txt
+ /// "If an error is returned then it must be guaranteed that no bytes were read."
+ /// ```
+ ///
+ /// Whether this applies to errors that occur while reading the encrypted data depends on the
+ /// underlying reader. If the error occurs while verifying the HMAC, the reader might become
+ /// practically unusable, since its position after the error is not known.
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ if self.data_remaining == 0 {
+ return Ok(0);
+ }
+
+ // get the number of bytes to read, compare as u64 to make sure we can read more than
+ // 2^32 bytes even on 32 bit systems.
+ let bytes_to_read = self.data_remaining.min(buf.len() as u64) as usize;
+ let read = self.reader.read(&mut buf[0..bytes_to_read])?;
+ self.data_remaining -= read as u64;
+
+ // Update the hmac with the encrypted data
+ self.hmac.update(&buf[0..read]);
+
+ // decrypt the data
+ self.cipher.crypt_in_place(&mut buf[0..read]);
+
+ // if there is no data left to read, check the integrity of the data
+ if self.data_remaining == 0 {
+ assert!(
+ !self.finalized,
+ "Tried to use an already finalized HMAC. This is a bug!"
+ );
+ self.finalized = true;
+
+ // Zip uses HMAC-Sha1-80, which only uses the first half of the hash
+ // see https://www.winzip.com/win/en/aes_info.html#auth-faq
+ let mut read_auth_code = [0; AUTH_CODE_LENGTH];
+ self.reader.read_exact(&mut read_auth_code)?;
+ let computed_auth_code = &self.hmac.finalize_reset().into_bytes()[0..AUTH_CODE_LENGTH];
+
+ // use constant time comparison to mitigate timing attacks
+ if !constant_time_eq(computed_auth_code, &read_auth_code) {
+ return Err(
+ io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Invalid authentication code, this could be due to an invalid password or errors in the data"
+ )
+ );
+ }
+ }
+
+ Ok(read)
+ }
+}
+
+impl<R: Read> AesReaderValid<R> {
+ /// Consumes this decoder, returning the underlying reader.
+ pub fn into_inner(self) -> R {
+ self.reader
+ }
+}