Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 42 additions & 40 deletions src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -927,19 +927,26 @@ impl<W: Write + Seek> ZipWriter<W> {
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)
Expand All @@ -949,18 +956,6 @@ impl<W: Write + Seek> ZipWriter<W> {
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::<ZipLocalEntryBlock>() as u64 + name.to_string().len() as u64;

Expand Down Expand Up @@ -1090,29 +1085,22 @@ impl<W: Write + Seek> ZipWriter<W> {
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))?;
}
Expand Down Expand Up @@ -2004,11 +1992,25 @@ fn clamp_opt<T: Ord + Copy, U: Ord + Copy + TryFrom<T>>(
}
}

fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData) -> ZipResult<()> {
let Some((aes_mode, version, compression_method)) = file.aes_mode else {
fn update_aes_extra_data<W: Write + Seek>(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(
Expand All @@ -2023,11 +2025,11 @@ fn update_aes_extra_data<W: Write + Seek>(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())?;

Expand Down