Skip to content

Commit d972f3b

Browse files
committed
Fix AES encryption streaming support
The update_aes_extra_data function requires the underlying writer to support seeking to succeed. In effect, this function is only used to switch the mode between AE2 and AE1 depending on file size. This is not strictly necessary so long as we use the strong mode by default, so we now only call this function for the non-streaming case.
1 parent f628624 commit d972f3b

File tree

1 file changed

+42
-40
lines changed

1 file changed

+42
-40
lines changed

src/write.rs

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -927,19 +927,26 @@ impl<W: Write + Seek> ZipWriter<W> {
927927
new_extra_data.append(&mut extra_data);
928928
extra_data = new_extra_data;
929929
}
930+
931+
// Figure out the underlying compression_method and aes mode when using
932+
// AES encryption.
933+
let (compression_method, aes_mode) = match options.encrypt_with {
934+
// Preserve AES method for raw copies without needing a password
935+
#[cfg(feature = "aes-crypto")]
936+
None if options.aes_mode.is_some() => (CompressionMethod::Aes, options.aes_mode),
937+
#[cfg(feature = "aes-crypto")]
938+
Some(EncryptWith::Aes { mode, .. }) => (
939+
CompressionMethod::Aes,
940+
Some((mode, AesVendorVersion::Ae2, options.compression_method)),
941+
),
942+
_ => (options.compression_method, None),
943+
};
944+
930945
// Write AES encryption extra data.
931946
#[allow(unused_mut)]
932947
let mut aes_extra_data_start = 0;
933948
#[cfg(feature = "aes-crypto")]
934-
if let Some(EncryptWith::Aes { mode, .. }) = options.encrypt_with {
935-
let aes_dummy_extra_data = [0x02, 0x00, 0x41, 0x45, mode as u8, 0x00, 0x00];
936-
aes_extra_data_start = extra_data.len() as u64;
937-
ExtendedFileOptions::add_extra_data_unchecked(
938-
&mut extra_data,
939-
0x9901,
940-
&aes_dummy_extra_data,
941-
)?;
942-
} else if let Some((mode, vendor, underlying)) = options.aes_mode {
949+
if let Some((mode, vendor, underlying)) = aes_mode {
943950
// For raw copies of AES entries, write the correct AES extra data immediately
944951
let mut body = [0; 7];
945952
[body[0], body[1]] = (vendor as u16).to_le_bytes(); // vendor version (1 or 2)
@@ -949,18 +956,6 @@ impl<W: Write + Seek> ZipWriter<W> {
949956
aes_extra_data_start = extra_data.len() as u64;
950957
ExtendedFileOptions::add_extra_data_unchecked(&mut extra_data, 0x9901, &body)?;
951958
}
952-
953-
let (compression_method, aes_mode) = match options.encrypt_with {
954-
// Preserve AES method for raw copies without needing a password
955-
#[cfg(feature = "aes-crypto")]
956-
None if options.aes_mode.is_some() => (CompressionMethod::Aes, options.aes_mode),
957-
#[cfg(feature = "aes-crypto")]
958-
Some(EncryptWith::Aes { mode, .. }) => (
959-
CompressionMethod::Aes,
960-
Some((mode, AesVendorVersion::Ae2, options.compression_method)),
961-
),
962-
_ => (options.compression_method, None),
963-
};
964959
let header_end =
965960
header_start + size_of::<ZipLocalEntryBlock>() as u64 + name.to_string().len() as u64;
966961

@@ -1090,29 +1085,22 @@ impl<W: Write + Seek> ZipWriter<W> {
10901085
let file_end = writer.stream_position()?;
10911086
debug_assert!(file_end >= self.stats.start);
10921087
file.compressed_size = file_end - self.stats.start;
1093-
let mut crc = true;
1094-
if let Some(aes_mode) = &mut file.aes_mode {
1095-
// We prefer using AE-1 which provides an extra CRC check, but for small files we
1096-
// switch to AE-2 to prevent being able to use the CRC value to to reconstruct the
1097-
// unencrypted contents.
1098-
//
1099-
// C.f. https://www.winzip.com/en/support/aes-encryption/#crc-faq
1100-
aes_mode.1 = if self.stats.bytes_written < 20 {
1101-
crc = false;
1102-
AesVendorVersion::Ae2
1103-
} else {
1104-
AesVendorVersion::Ae1
1105-
};
1106-
}
1088+
1089+
let crc = !matches!(file.aes_mode, Some((_, AesVendorVersion::Ae2, _)));
1090+
11071091
file.crc32 = if crc {
11081092
self.stats.hasher.clone().finalize()
11091093
} else {
11101094
0
11111095
};
1112-
update_aes_extra_data(writer, file)?;
1096+
11131097
if file.using_data_descriptor {
11141098
write_data_descriptor(writer, file)?;
11151099
} else {
1100+
// Not using a data descriptor means the underlying writer
1101+
// supports seeking, so we can go back and update the AES Extra
1102+
// Data header to use AE1 for large files.
1103+
update_aes_extra_data(writer, file, self.stats.bytes_written)?;
11161104
update_local_file_header(writer, file)?;
11171105
writer.seek(SeekFrom::Start(file_end))?;
11181106
}
@@ -2004,11 +1992,25 @@ fn clamp_opt<T: Ord + Copy, U: Ord + Copy + TryFrom<T>>(
20041992
}
20051993
}
20061994

2007-
fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData) -> ZipResult<()> {
2008-
let Some((aes_mode, version, compression_method)) = file.aes_mode else {
1995+
fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData, bytes_written: u64) -> ZipResult<()> {
1996+
let Some((aes_mode, version, compression_method)) = &mut file.aes_mode else {
20091997
return Ok(());
20101998
};
20111999

2000+
// We prefer using AE-1 which provides an extra CRC check, but for small files we
2001+
// switch to AE-2 to prevent being able to use the CRC value to to reconstruct the
2002+
// unencrypted contents.
2003+
//
2004+
// We can only do this when the underlying writer supports
2005+
// seek operations, so we gate this behind using_data_descriptor.
2006+
//
2007+
// C.f. https://www.winzip.com/en/support/aes-encryption/#crc-faq
2008+
*version = if bytes_written < 20 {
2009+
AesVendorVersion::Ae2
2010+
} else {
2011+
AesVendorVersion::Ae1
2012+
};
2013+
20122014
let extra_data_start = file.extra_data_start.unwrap();
20132015

20142016
writer.seek(SeekFrom::Start(
@@ -2023,11 +2025,11 @@ fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData
20232025
// Data size.
20242026
buf.write_u16_le(7)?;
20252027
// Integer version number.
2026-
buf.write_u16_le(version as u16)?;
2028+
buf.write_u16_le(*version as u16)?;
20272029
// Vendor ID.
20282030
buf.write_all(b"AE")?;
20292031
// AES encryption strength.
2030-
buf.write_all(&[aes_mode as u8])?;
2032+
buf.write_all(&[*aes_mode as u8])?;
20312033
// Real compression method.
20322034
buf.write_u16_le(compression_method.serialize_to_u16())?;
20332035

0 commit comments

Comments
 (0)