|  | //! 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 | 
|  | } | 
|  | } |