[DRAFT][WIP]Add Zip Archives password support#122093
[DRAFT][WIP]Add Zip Archives password support#122093alinpahontu2912 wants to merge 58 commits intodotnet:mainfrom
Conversation
…assword and unprotected
… with cryptography lib
src/libraries/System.IO.Compression/tests/ZipCryptoStreamWrappedConformanceTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/tests/ZipCryptoStreamWrappedConformanceTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/tests/ZipCryptoStreamWrappedConformanceTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/tests/WinZipAesStreamConformanceTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/tests/WinZipAesStreamWrappedConformanceTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs
Show resolved
Hide resolved
src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/tests/ZipCryptoStreamWrappedConformanceTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAesStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
Outdated
Show resolved
Hide resolved
...stem.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs
Show resolved
Hide resolved
src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipBlocks.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs
Show resolved
Hide resolved
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs
Outdated
Show resolved
Hide resolved
...O.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.IO.Compression/tests/ZipCryptoStreamWrappedConformanceTests.cs
Outdated
Show resolved
Hide resolved
| public async Task<Stream> OpenAsync(string password, EncryptionMethod encryptionMethod, CancellationToken cancellationToken = default) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
| ThrowIfInvalidArchive(); | ||
| if (string.IsNullOrEmpty(password)) | ||
| { | ||
| throw new ArgumentNullException(nameof(password), SR.EmptyPassword); | ||
| } | ||
|
|
||
| switch (_archive.Mode) | ||
| { | ||
| case ZipArchiveMode.Read: | ||
| if (!IsEncrypted) | ||
| { | ||
| throw new InvalidDataException(SR.EntryNotEncrypted); | ||
| } | ||
| return await OpenInReadModeAsync(checkOpenable: true, cancellationToken, password.AsMemory()).ConfigureAwait(false); | ||
| case ZipArchiveMode.Create: | ||
| return OpenInWriteMode(password, encryptionMethod); | ||
| case ZipArchiveMode.Update: | ||
| default: | ||
| Debug.Assert(_archive.Mode == ZipArchiveMode.Update); | ||
| if (!IsEncrypted) | ||
| { | ||
| throw new InvalidDataException(SR.EntryNotEncrypted); | ||
| } | ||
| return await OpenInUpdateModeAsync(loadExistingContent: true, cancellationToken, password).ConfigureAwait(false); | ||
| } |
There was a problem hiding this comment.
OpenAsync(string password, EncryptionMethod encryptionMethod, ...) currently allows Read/Update modes and ignores encryptionMethod, which contradicts the sync Open(string password, EncryptionMethod) behavior (Read throws; Update throws). This can lead to callers thinking they’re controlling encryption when they aren’t. Align the async overload with the sync contract (only Create mode should accept an encryption method; Read/Update should throw the same exceptions as the sync overload).
|
|
||
| // Derive keys using PBKDF2 | ||
| int maxPasswordByteCount = Encoding.UTF8.GetMaxByteCount(password.Length); | ||
| Span<byte> passwordBytes = stackalloc byte[maxPasswordByteCount]; |
There was a problem hiding this comment.
Potential stack overflow if the length of password isn't constrained.
| { | ||
| int saltSize = GetSaltSize(keySizeBits); | ||
| int keySizeBytes = keySizeBits / 8; | ||
| int expectedSize = saltSize + keySizeBytes + keySizeBytes + 2; |
There was a problem hiding this comment.
Cryptographic code should normally be within a checked block to help avoid edge case misuse.
Can you instead create a shared structure or tuple for all of this? That way you won't have to parse it every time you open a file, and it means you can consolidate all of the validation logic to the initial tuple creation rather than have the logic spread out across lots of different methods: this method, plus WrapWithDecryptionIfNeeded, plus CreateKey.
| } | ||
|
|
||
| // Verify the salt matches | ||
| Debug.Assert(fileSalt.AsSpan().SequenceEqual(expectedSalt), "Salt mismatch - key material does not match this entry."); |
There was a problem hiding this comment.
Why Debug.Assert rather than a proper runtime check?
| } | ||
|
|
||
| // Compare the first 10 bytes of the expected hash | ||
| if (!storedAuth.AsSpan().SequenceEqual(expectedAuth.AsSpan(0, 10))) |
There was a problem hiding this comment.
This should probably use FixedTimeEquals (https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals). Consider replacing all uses of SequenceEqual in this class with calls to FixedTimeEquals if appropriate.
|
|
||
| private void IncrementCounter() | ||
| { | ||
| // WinZip AES treats the entire 16-byte block as a little-endian 128-bit integer |
There was a problem hiding this comment.
Why not use a proper UInt128 field instead? You can use BinaryPrimitives to convert it to and from a little-endian binary representation.
Fixes #1545
Big Milestones and status: