diff --git a/src/write.rs b/src/write.rs index 64e85a4de..b41604d0b 100644 --- a/src/write.rs +++ b/src/write.rs @@ -927,19 +927,26 @@ impl ZipWriter { new_extra_data.append(&mut extra_data); extra_data = new_extra_data; } + + // Figure out the underlying compression_method and aes mode when using + // AES encryption. + let (compression_method, aes_mode) = match options.encrypt_with { + // Preserve AES method for raw copies without needing a password + #[cfg(feature = "aes-crypto")] + None if options.aes_mode.is_some() => (CompressionMethod::Aes, options.aes_mode), + #[cfg(feature = "aes-crypto")] + Some(EncryptWith::Aes { mode, .. }) => ( + CompressionMethod::Aes, + Some((mode, AesVendorVersion::Ae2, options.compression_method)), + ), + _ => (options.compression_method, None), + }; + // Write AES encryption extra data. #[allow(unused_mut)] let mut aes_extra_data_start = 0; #[cfg(feature = "aes-crypto")] - if let Some(EncryptWith::Aes { mode, .. }) = options.encrypt_with { - let aes_dummy_extra_data = [0x02, 0x00, 0x41, 0x45, mode as u8, 0x00, 0x00]; - aes_extra_data_start = extra_data.len() as u64; - ExtendedFileOptions::add_extra_data_unchecked( - &mut extra_data, - 0x9901, - &aes_dummy_extra_data, - )?; - } else if let Some((mode, vendor, underlying)) = options.aes_mode { + if let Some((mode, vendor, underlying)) = aes_mode { // For raw copies of AES entries, write the correct AES extra data immediately let mut body = [0; 7]; [body[0], body[1]] = (vendor as u16).to_le_bytes(); // vendor version (1 or 2) @@ -949,18 +956,6 @@ impl ZipWriter { aes_extra_data_start = extra_data.len() as u64; ExtendedFileOptions::add_extra_data_unchecked(&mut extra_data, 0x9901, &body)?; } - - let (compression_method, aes_mode) = match options.encrypt_with { - // Preserve AES method for raw copies without needing a password - #[cfg(feature = "aes-crypto")] - None if options.aes_mode.is_some() => (CompressionMethod::Aes, options.aes_mode), - #[cfg(feature = "aes-crypto")] - Some(EncryptWith::Aes { mode, .. }) => ( - CompressionMethod::Aes, - Some((mode, AesVendorVersion::Ae2, options.compression_method)), - ), - _ => (options.compression_method, None), - }; let header_end = header_start + size_of::() as u64 + name.to_string().len() as u64; @@ -1090,29 +1085,22 @@ impl ZipWriter { let file_end = writer.stream_position()?; debug_assert!(file_end >= self.stats.start); file.compressed_size = file_end - self.stats.start; - let mut crc = true; - if let Some(aes_mode) = &mut file.aes_mode { - // We prefer using AE-1 which provides an extra CRC check, but for small files we - // switch to AE-2 to prevent being able to use the CRC value to to reconstruct the - // unencrypted contents. - // - // C.f. https://www.winzip.com/en/support/aes-encryption/#crc-faq - aes_mode.1 = if self.stats.bytes_written < 20 { - crc = false; - AesVendorVersion::Ae2 - } else { - AesVendorVersion::Ae1 - }; - } + + let crc = !matches!(file.aes_mode, Some((_, AesVendorVersion::Ae2, _))); + file.crc32 = if crc { self.stats.hasher.clone().finalize() } else { 0 }; - update_aes_extra_data(writer, file)?; + if file.using_data_descriptor { write_data_descriptor(writer, file)?; } else { + // Not using a data descriptor means the underlying writer + // supports seeking, so we can go back and update the AES Extra + // Data header to use AE1 for large files. + update_aes_extra_data(writer, file, self.stats.bytes_written)?; update_local_file_header(writer, file)?; writer.seek(SeekFrom::Start(file_end))?; } @@ -2004,11 +1992,25 @@ fn clamp_opt>( } } -fn update_aes_extra_data(writer: &mut W, file: &mut ZipFileData) -> ZipResult<()> { - let Some((aes_mode, version, compression_method)) = file.aes_mode else { +fn update_aes_extra_data(writer: &mut W, file: &mut ZipFileData, bytes_written: u64) -> ZipResult<()> { + let Some((aes_mode, version, compression_method)) = &mut file.aes_mode else { return Ok(()); }; + // We prefer using AE-1 which provides an extra CRC check, but for small files we + // switch to AE-2 to prevent being able to use the CRC value to to reconstruct the + // unencrypted contents. + // + // We can only do this when the underlying writer supports + // seek operations, so we gate this behind using_data_descriptor. + // + // C.f. https://www.winzip.com/en/support/aes-encryption/#crc-faq + *version = if bytes_written < 20 { + AesVendorVersion::Ae2 + } else { + AesVendorVersion::Ae1 + }; + let extra_data_start = file.extra_data_start.unwrap(); writer.seek(SeekFrom::Start( @@ -2023,11 +2025,11 @@ fn update_aes_extra_data(writer: &mut W, file: &mut ZipFileData // Data size. buf.write_u16_le(7)?; // Integer version number. - buf.write_u16_le(version as u16)?; + buf.write_u16_le(*version as u16)?; // Vendor ID. buf.write_all(b"AE")?; // AES encryption strength. - buf.write_all(&[aes_mode as u8])?; + buf.write_all(&[*aes_mode as u8])?; // Real compression method. buf.write_u16_le(compression_method.serialize_to_u16())?;