Skip to content

Commit feef040

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 feef040

File tree

1 file changed

+41
-35
lines changed

1 file changed

+41
-35
lines changed

src/write.rs

Lines changed: 41 additions & 35 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,40 @@ 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-
};
1088+
1089+
if !file.using_data_descriptor {
1090+
if let Some(aes_mode) = &mut file.aes_mode {
1091+
// We prefer using AE-1 which provides an extra CRC check, but for small files we
1092+
// switch to AE-2 to prevent being able to use the CRC value to to reconstruct the
1093+
// unencrypted contents.
1094+
//
1095+
// We can only do this when the underlying writer supports
1096+
// seek operations, so we gate this behind using_data_descriptor.
1097+
//
1098+
// C.f. https://www.winzip.com/en/support/aes-encryption/#crc-faq
1099+
aes_mode.1 = if self.stats.bytes_written < 20 {
1100+
AesVendorVersion::Ae2
1101+
} else {
1102+
AesVendorVersion::Ae1
1103+
};
1104+
}
11061105
}
1106+
1107+
let crc = !matches!(file.aes_mode, Some((_, AesVendorVersion::Ae2, _)));
1108+
11071109
file.crc32 = if crc {
11081110
self.stats.hasher.clone().finalize()
11091111
} else {
11101112
0
11111113
};
1112-
update_aes_extra_data(writer, file)?;
1114+
11131115
if file.using_data_descriptor {
11141116
write_data_descriptor(writer, file)?;
11151117
} else {
1118+
// If using a data descriptor, it's because the underlying
1119+
// Write implementation doesn't support seek. In this case, we
1120+
// shouldn't update the AES Extra Data.
1121+
update_aes_extra_data(writer, file)?;
11161122
update_local_file_header(writer, file)?;
11171123
writer.seek(SeekFrom::Start(file_end))?;
11181124
}

0 commit comments

Comments
 (0)