From 8cf8ad57081c6013e2ca9324ef8eba8869d932f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:30:44 +0000 Subject: [PATCH 01/10] Exclude ZIP encryption crypto from browser builds Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- .../src/Resources/Strings.resx | 3 ++ .../src/System.IO.Compression.csproj | 9 ++--- .../IO/Compression/ZipArchiveEntry.Async.cs | 4 +-- .../System/IO/Compression/ZipArchiveEntry.cs | 4 +-- .../ZipEncryption.PlatformNotSupported.cs | 36 +++++++++++++++++++ .../tests/ZipArchive/zip_ReadTests.cs | 22 ++++++++++++ .../tests/ZipCryptoStreamConformanceTests.cs | 2 +- .../wasm/Wasm.Build.Tests/NativeBuildTests.cs | 2 ++ 8 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryption.PlatformNotSupported.cs diff --git a/src/libraries/System.IO.Compression/src/Resources/Strings.resx b/src/libraries/System.IO.Compression/src/Resources/Strings.resx index 8f70a3e2dea3d2..47dd45445299ab 100644 --- a/src/libraries/System.IO.Compression/src/Resources/Strings.resx +++ b/src/libraries/System.IO.Compression/src/Resources/Strings.resx @@ -426,6 +426,9 @@ WinZip AES encryption is not supported on the browser platform. + + ZIP encryption is not supported on the browser platform. + The entry's encryption method is not supported. diff --git a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj index c71e8f1e4b181f..698300f947ac5f 100644 --- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj +++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj @@ -65,9 +65,10 @@ - - - + + + + @@ -111,7 +112,7 @@ - + diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs index 6a74dc6f308525..274c09cd07e9c2 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs @@ -728,7 +728,7 @@ private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, Can ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime); - ZipCryptoStream encryptionStream = ZipCryptoStream.Create( + Stream encryptionStream = ZipCryptoStream.Create( baseStream: _archive.ArchiveStream, keys: _derivedZipCryptoKeyMaterial.Value, passwordVerifierLow2Bytes: verifierLow2Bytes, @@ -782,7 +782,7 @@ private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, Can // The AES extra field stores the real compression method bool useDeflate = _compressionLevel != CompressionLevel.NoCompression; - WinZipAesStream encryptionStream = WinZipAesStream.Create( + Stream encryptionStream = WinZipAesStream.Create( baseStream: _archive.ArchiveStream, keyMaterial: _derivedAesKeyMaterial.Value, totalStreamSize: -1, diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs index f6f0dfc2af3106..76c1f2c4692df3 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs @@ -1776,7 +1776,7 @@ private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite) ushort verifierLow2Bytes = (ushort)ZipHelper.DateTimeToDosTime(_lastModified.DateTime); - using (ZipCryptoStream encryptionStream = ZipCryptoStream.Create( + using (Stream encryptionStream = ZipCryptoStream.Create( baseStream: _archive.ArchiveStream, keys: _derivedZipCryptoKeyMaterial.Value, passwordVerifierLow2Bytes: verifierLow2Bytes, @@ -1807,7 +1807,7 @@ private unsafe void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite) bool useDeflate = _compressionLevel != CompressionLevel.NoCompression; - using (WinZipAesStream encryptionStream = WinZipAesStream.Create( + using (Stream encryptionStream = WinZipAesStream.Create( baseStream: _archive.ArchiveStream, keyMaterial: _derivedAesKeyMaterial.Value, totalStreamSize: -1, diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryption.PlatformNotSupported.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryption.PlatformNotSupported.cs new file mode 100644 index 00000000000000..2fa6cad6621997 --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryption.PlatformNotSupported.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using System.Threading.Tasks; + +namespace System.IO.Compression +{ + internal readonly struct WinZipAesKeyMaterial; + + internal static class WinZipAesStream + { + internal static int GetSaltSize(int keySizeBits) => throw CreateException(); + + internal static WinZipAesKeyMaterial CreateKey(ReadOnlySpan password, byte[]? salt, int keySizeBits) => throw CreateException(); + + internal static Stream Create(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypting, bool leaveOpen = false) => throw CreateException(); + + internal static Task CreateAsync(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypting, bool leaveOpen = false, CancellationToken cancellationToken = default) => throw CreateException(); + + private static PlatformNotSupportedException CreateException() => new(SR.ZipEncryptionNotSupportedOnBrowser); + } + + internal static class ZipCryptoStream + { + internal static ZipCryptoKeys CreateKey(ReadOnlySpan password) => throw CreateException(); + + internal static Stream Create(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encrypting, bool leaveOpen = false) => throw CreateException(); + + internal static Task CreateAsync(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encrypting, CancellationToken cancellationToken = default, bool leaveOpen = false) => throw CreateException(); + + internal static Stream Create(Stream baseStream, ZipCryptoKeys keys, ushort passwordVerifierLow2Bytes, bool encrypting, uint? crc32 = null, bool leaveOpen = false) => throw CreateException(); + + private static PlatformNotSupportedException CreateException() => new(SR.ZipEncryptionNotSupportedOnBrowser); + } +} diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs index 8ab7802ff61ac5..69e763579ff74b 100644 --- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs @@ -1052,6 +1052,28 @@ public async Task DecryptEntries_MixedEncryptions(bool async) await DisposeZipArchive(async, archive); } + [Theory] + [MemberData(nameof(Get_Booleans_Data))] + [PlatformSpecific(TestPlatforms.Browser)] + public async Task DecryptZipCryptoEntry_ThrowsPlatformNotSupported(bool async) + { + using Stream archiveStream = await StreamHelpers.CreateTempCopyStream(passwordProtected("PasswordProtected_MixedEncryptions.zip")); + ZipArchive archive = await CreateZipArchive(async, archiveStream, ZipArchiveMode.Read); + + try + { + ZipArchiveEntry entry = archive.GetEntry("hello.txt"); + Assert.NotNull(entry); + Assert.Equal(ZipEncryptionMethod.ZipCrypto, entry.EncryptionMethod); + + await Assert.ThrowsAsync(() => OpenEntryStream(async, entry, "S3cur3P@ssw0rd")); + } + finally + { + await DisposeZipArchive(async, archive); + } + } + [Theory] [MemberData(nameof(Get_Booleans_Data))] [SkipOnPlatform(TestPlatforms.Browser, "WinZip AES encryption is not supported on browser.")] diff --git a/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs b/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs index 3d89614d329d84..1d6f571194ea62 100644 --- a/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs @@ -125,6 +125,6 @@ static ZipCryptoStreamConformanceTests() // the tests are correctly skipped instead of failing during type initialization. internal static class ZipCryptoStreamTestsSupport { - public static bool IsSupported => PlatformDetection.IsReflectionEmitSupported && PlatformDetection.IsMultithreadingSupported; + public static bool IsSupported => !PlatformDetection.IsBrowser && PlatformDetection.IsReflectionEmitSupported && PlatformDetection.IsMultithreadingSupported; } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs index 3e107ad21bb180..93f450bf1bf73e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs @@ -115,6 +115,8 @@ public async Task ZipArchiveInteropTest() Configuration config = Configuration.Debug; ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ZipArchiveInteropTest", extraProperties: "true"); BuildProject(info, config, new BuildOptions(AssertAppBundle: false)); + BuildPaths paths = GetBuildPaths(config, forPublish: false); + Assert.DoesNotContain("System_Security_Cryptography", File.ReadAllText(Path.Combine(paths.ObjWasmDir, "driver-gen.c"))); RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions(config, TestScenario: "ZipArchiveInteropTest")); Assert.Collection( result.TestOutput, From c315ffbbd97565c45df9dccdaa6885fc1ab2fd50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:34:55 +0000 Subject: [PATCH 02/10] Preserve browser ZipCrypto support Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- .../src/Resources/Strings.resx | 4 +--- .../src/System.IO.Compression.csproj | 4 ++-- ...d.cs => WinZipAes.PlatformNotSupported.cs} | 15 +------------ .../System/IO/Compression/ZipCryptoStream.cs | 7 +++--- .../tests/ZipArchive/zip_ReadTests.cs | 22 +++++++++---------- .../tests/ZipCryptoStreamConformanceTests.cs | 2 +- 6 files changed, 19 insertions(+), 35 deletions(-) rename src/libraries/System.IO.Compression/src/System/IO/Compression/{ZipEncryption.PlatformNotSupported.cs => WinZipAes.PlatformNotSupported.cs} (52%) diff --git a/src/libraries/System.IO.Compression/src/Resources/Strings.resx b/src/libraries/System.IO.Compression/src/Resources/Strings.resx index 47dd45445299ab..dcded6ccfb88e1 100644 --- a/src/libraries/System.IO.Compression/src/Resources/Strings.resx +++ b/src/libraries/System.IO.Compression/src/Resources/Strings.resx @@ -426,9 +426,7 @@ WinZip AES encryption is not supported on the browser platform. - - ZIP encryption is not supported on the browser platform. - + The entry's encryption method is not supported. diff --git a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj index 698300f947ac5f..2a30f33bbc0f96 100644 --- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj +++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj @@ -65,10 +65,10 @@ - + - + diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryption.PlatformNotSupported.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAes.PlatformNotSupported.cs similarity index 52% rename from src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryption.PlatformNotSupported.cs rename to src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAes.PlatformNotSupported.cs index 2fa6cad6621997..75c9de825295e3 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipEncryption.PlatformNotSupported.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/WinZipAes.PlatformNotSupported.cs @@ -18,19 +18,6 @@ internal static class WinZipAesStream internal static Task CreateAsync(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypting, bool leaveOpen = false, CancellationToken cancellationToken = default) => throw CreateException(); - private static PlatformNotSupportedException CreateException() => new(SR.ZipEncryptionNotSupportedOnBrowser); - } - - internal static class ZipCryptoStream - { - internal static ZipCryptoKeys CreateKey(ReadOnlySpan password) => throw CreateException(); - - internal static Stream Create(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encrypting, bool leaveOpen = false) => throw CreateException(); - - internal static Task CreateAsync(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encrypting, CancellationToken cancellationToken = default, bool leaveOpen = false) => throw CreateException(); - - internal static Stream Create(Stream baseStream, ZipCryptoKeys keys, ushort passwordVerifierLow2Bytes, bool encrypting, uint? crc32 = null, bool leaveOpen = false) => throw CreateException(); - - private static PlatformNotSupportedException CreateException() => new(SR.ZipEncryptionNotSupportedOnBrowser); + private static PlatformNotSupportedException CreateException() => new(SR.WinZipEncryptionNotSupportedOnBrowser); } } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs index 3243c56566ebb0..0381a9a2c7ed9e 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs @@ -4,7 +4,6 @@ using System.Buffers; using System.Buffers.Binary; using System.Diagnostics; -using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -138,7 +137,7 @@ internal static ZipCryptoKeys CreateKey(ReadOnlySpan password) } finally { - CryptographicOperations.ZeroMemory(passwordBytes); + Array.Clear(passwordBytes); ArrayPool.Shared.Return(passwordBytes); } @@ -150,7 +149,9 @@ private void CalculateHeader(Span header) Debug.Assert(header.Length == 12); // bytes 0..9 random - RandomNumberGenerator.Fill(header.Slice(0, 10)); + Span randomBytes = stackalloc byte[16]; + Guid.NewGuid().TryWriteBytes(randomBytes); + randomBytes.Slice(0, 10).CopyTo(header); // bytes 10..11 verifier if (_crc32ForHeader.HasValue) diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs index 69e763579ff74b..e79f9eeead138e 100644 --- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_ReadTests.cs @@ -1055,23 +1055,21 @@ public async Task DecryptEntries_MixedEncryptions(bool async) [Theory] [MemberData(nameof(Get_Booleans_Data))] [PlatformSpecific(TestPlatforms.Browser)] - public async Task DecryptZipCryptoEntry_ThrowsPlatformNotSupported(bool async) + public async Task DecryptZipCryptoEntry_Browser(bool async) { + const string Password = "S3cur3P@ssw0rd"; using Stream archiveStream = await StreamHelpers.CreateTempCopyStream(passwordProtected("PasswordProtected_MixedEncryptions.zip")); ZipArchive archive = await CreateZipArchive(async, archiveStream, ZipArchiveMode.Read); - try - { - ZipArchiveEntry entry = archive.GetEntry("hello.txt"); - Assert.NotNull(entry); - Assert.Equal(ZipEncryptionMethod.ZipCrypto, entry.EncryptionMethod); + ZipArchiveEntry entry = archive.GetEntry("hello.txt"); + Assert.NotNull(entry); + Assert.Equal(ZipEncryptionMethod.ZipCrypto, entry.EncryptionMethod); - await Assert.ThrowsAsync(() => OpenEntryStream(async, entry, "S3cur3P@ssw0rd")); - } - finally - { - await DisposeZipArchive(async, archive); - } + using Stream entryStream = await OpenEntryStream(async, entry, Password); + using StreamReader reader = new(entryStream); + Assert.Equal("Hello", reader.ReadToEnd().TrimEnd()); + + await DisposeZipArchive(async, archive); } [Theory] diff --git a/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs b/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs index 1d6f571194ea62..3d89614d329d84 100644 --- a/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipCryptoStreamConformanceTests.cs @@ -125,6 +125,6 @@ static ZipCryptoStreamConformanceTests() // the tests are correctly skipped instead of failing during type initialization. internal static class ZipCryptoStreamTestsSupport { - public static bool IsSupported => !PlatformDetection.IsBrowser && PlatformDetection.IsReflectionEmitSupported && PlatformDetection.IsMultithreadingSupported; + public static bool IsSupported => PlatformDetection.IsReflectionEmitSupported && PlatformDetection.IsMultithreadingSupported; } } From 119fe70e42185b61eeb8ce40c02f3cfa8930d317 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 08:34:19 +0000 Subject: [PATCH 03/10] Check WASM P/Invoke table in ZIP test Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs index 93f450bf1bf73e..0e90507526bd2f 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs @@ -116,7 +116,7 @@ public async Task ZipArchiveInteropTest() ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ZipArchiveInteropTest", extraProperties: "true"); BuildProject(info, config, new BuildOptions(AssertAppBundle: false)); BuildPaths paths = GetBuildPaths(config, forPublish: false); - Assert.DoesNotContain("System_Security_Cryptography", File.ReadAllText(Path.Combine(paths.ObjWasmDir, "driver-gen.c"))); + Assert.DoesNotContain("System_Security_Cryptography", File.ReadAllText(Path.Combine(paths.ObjWasmDir, "pinvoke-table.h"))); RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions(config, TestScenario: "ZipArchiveInteropTest")); Assert.Collection( result.TestOutput, From 7ad60748d6aeb2007fb23b8e3eceaa1b26bb6275 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 08:48:36 +0000 Subject: [PATCH 04/10] Handle both WASM runtime table names Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs index 0e90507526bd2f..5473271b457946 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs @@ -116,7 +116,8 @@ public async Task ZipArchiveInteropTest() ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ZipArchiveInteropTest", extraProperties: "true"); BuildProject(info, config, new BuildOptions(AssertAppBundle: false)); BuildPaths paths = GetBuildPaths(config, forPublish: false); - Assert.DoesNotContain("System_Security_Cryptography", File.ReadAllText(Path.Combine(paths.ObjWasmDir, "pinvoke-table.h"))); + string pinvokeTableFileName = IsCoreClrRuntime ? "pinvoke-table.cpp" : "pinvoke-table.h"; + Assert.DoesNotContain("System_Security_Cryptography", File.ReadAllText(Path.Combine(paths.ObjWasmDir, pinvokeTableFileName))); RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions(config, TestScenario: "ZipArchiveInteropTest")); Assert.Collection( result.TestOutput, From 88e7cf08817d75932d5514c0e90d264ffce9fd9f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 08:53:10 +0000 Subject: [PATCH 05/10] Use CoreCLR PInvoke output name Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs index 5473271b457946..e0d1fad124f520 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs @@ -116,7 +116,7 @@ public async Task ZipArchiveInteropTest() ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ZipArchiveInteropTest", extraProperties: "true"); BuildProject(info, config, new BuildOptions(AssertAppBundle: false)); BuildPaths paths = GetBuildPaths(config, forPublish: false); - string pinvokeTableFileName = IsCoreClrRuntime ? "pinvoke-table.cpp" : "pinvoke-table.h"; + string pinvokeTableFileName = IsCoreClrRuntime ? "callhelpers-pinvoke.cpp" : "pinvoke-table.h"; Assert.DoesNotContain("System_Security_Cryptography", File.ReadAllText(Path.Combine(paths.ObjWasmDir, pinvokeTableFileName))); RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions(config, TestScenario: "ZipArchiveInteropTest")); Assert.Collection( From d199061775f8c549ad5f16001a902d28ed8e4841 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:19:24 +0000 Subject: [PATCH 06/10] Use CSPRNG for ZipCrypto header bytes Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- .../src/System/IO/Compression/ZipCryptoStream.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs index 0381a9a2c7ed9e..bf3b80cfa11000 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Diagnostics; +using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -149,9 +150,9 @@ private void CalculateHeader(Span header) Debug.Assert(header.Length == 12); // bytes 0..9 random - Span randomBytes = stackalloc byte[16]; - Guid.NewGuid().TryWriteBytes(randomBytes); - randomBytes.Slice(0, 10).CopyTo(header); + Span randomBytes = stackalloc byte[10]; + RandomNumberGenerator.Fill(randomBytes); + randomBytes.CopyTo(header); // bytes 10..11 verifier if (_crc32ForHeader.HasValue) From 9b8d47cc5bf9bd7d18ae1e83d3a15a65135ad6fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:47:57 +0000 Subject: [PATCH 07/10] WIP: switch ZipCrypto header randomness to CSPRNG with browser fallback Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- .../src/System.IO.Compression.csproj | 2 ++ .../Compression/ZipCryptoStream.Random.Browser.cs | 15 +++++++++++++++ .../ZipCryptoStream.Random.NonBrowser.cs | 15 +++++++++++++++ .../src/System/IO/Compression/ZipCryptoStream.cs | 7 ++----- 4 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Browser.cs create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.NonBrowser.cs diff --git a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj index 2a30f33bbc0f96..b6ffb04cc72c5d 100644 --- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj +++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj @@ -66,6 +66,8 @@ + + diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Browser.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Browser.cs new file mode 100644 index 00000000000000..e04973563585d7 --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Browser.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Compression +{ + internal sealed partial class ZipCryptoStream + { + private static void FillHeaderRandomBytes(Span header) + { + Span guidBytes = stackalloc byte[16]; + Guid.NewGuid().TryWriteBytes(guidBytes); + guidBytes.Slice(0, 10).CopyTo(header); + } + } +} diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.NonBrowser.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.NonBrowser.cs new file mode 100644 index 00000000000000..56c63770d6fa69 --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.NonBrowser.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography; + +namespace System.IO.Compression +{ + internal sealed partial class ZipCryptoStream + { + private static void FillHeaderRandomBytes(Span header) + { + RandomNumberGenerator.Fill(header.Slice(0, 10)); + } + } +} diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs index bf3b80cfa11000..64450569184a34 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs @@ -4,14 +4,13 @@ using System.Buffers; using System.Buffers.Binary; using System.Diagnostics; -using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; namespace System.IO.Compression { - internal sealed class ZipCryptoStream : Stream + internal sealed partial class ZipCryptoStream : Stream { private const int EncryptionBufferSize = 4096; @@ -150,9 +149,7 @@ private void CalculateHeader(Span header) Debug.Assert(header.Length == 12); // bytes 0..9 random - Span randomBytes = stackalloc byte[10]; - RandomNumberGenerator.Fill(randomBytes); - randomBytes.CopyTo(header); + FillHeaderRandomBytes(header); // bytes 10..11 verifier if (_crc32ForHeader.HasValue) From 77c2d4c3dbe37e15a4cbef373f8b3ed20241522e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:41:52 +0000 Subject: [PATCH 08/10] Enforce CSPRNG for ZipCrypto header on browser and wasi Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- .../src/Resources/Strings.resx | 3 +++ .../src/System.IO.Compression.csproj | 5 ++-- ...p.GetCryptographicallySecureRandomBytes.cs | 13 +++++++++++ .../ZipCryptoStream.Random.Browser.cs | 15 ------------ .../ZipCryptoStream.Random.BrowserOrWasi.cs | 23 +++++++++++++++++++ ...r.cs => ZipCryptoStream.Random.Default.cs} | 0 6 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs delete mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Browser.cs create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.BrowserOrWasi.cs rename src/libraries/System.IO.Compression/src/System/IO/Compression/{ZipCryptoStream.Random.NonBrowser.cs => ZipCryptoStream.Random.Default.cs} (100%) diff --git a/src/libraries/System.IO.Compression/src/Resources/Strings.resx b/src/libraries/System.IO.Compression/src/Resources/Strings.resx index dcded6ccfb88e1..d2fb9df005cfba 100644 --- a/src/libraries/System.IO.Compression/src/Resources/Strings.resx +++ b/src/libraries/System.IO.Compression/src/Resources/Strings.resx @@ -426,6 +426,9 @@ WinZip AES encryption is not supported on the browser platform. + + Unable to generate cryptographically secure random bytes. + The entry's encryption method is not supported. diff --git a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj index b6ffb04cc72c5d..9af63969bfaef5 100644 --- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj +++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj @@ -66,8 +66,9 @@ - - + + + diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs new file mode 100644 index 00000000000000..bc93ee82bac327 --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetCryptographicallySecureRandomBytes")] + internal static unsafe partial int GetCryptographicallySecureRandomBytes(byte* buffer, int length); + } +} diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Browser.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Browser.cs deleted file mode 100644 index e04973563585d7..00000000000000 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Browser.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.IO.Compression -{ - internal sealed partial class ZipCryptoStream - { - private static void FillHeaderRandomBytes(Span header) - { - Span guidBytes = stackalloc byte[16]; - Guid.NewGuid().TryWriteBytes(guidBytes); - guidBytes.Slice(0, 10).CopyTo(header); - } - } -} diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.BrowserOrWasi.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.BrowserOrWasi.cs new file mode 100644 index 00000000000000..68df99653c4d70 --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.BrowserOrWasi.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Compression +{ + internal sealed partial class ZipCryptoStream + { + private static unsafe void FillHeaderRandomBytes(Span header) + { + // System.Security.Cryptography's RandomNumberGenerator is not referenced on browser and is + // not supported on wasi, so call the shared native CSPRNG directly. It is backed by + // crypto.getRandomValues() on browser and by wasi:random (getentropy/__wasi_random_get) on wasi. + Span randomBytes = header.Slice(0, 10); + fixed (byte* pRandomBytes = randomBytes) + { + if (Interop.Sys.GetCryptographicallySecureRandomBytes(pRandomBytes, randomBytes.Length) != 0) + { + throw new IOException(SR.UnableToGenerateRandomBytes); + } + } + } + } +} diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.NonBrowser.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Default.cs similarity index 100% rename from src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.NonBrowser.cs rename to src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.Random.Default.cs From 6ef6c9911024b1b34ffe51a99d8c74bc7fc5b55a Mon Sep 17 00:00:00 2001 From: Stefan-Alin Pahontu <56953855+alinpahontu2912@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:58:14 +0200 Subject: [PATCH 09/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Interop.GetCryptographicallySecureRandomBytes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs index bc93ee82bac327..128abfa506a90d 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Interop.GetCryptographicallySecureRandomBytes.cs @@ -7,7 +7,7 @@ internal static partial class Interop { internal static partial class Sys { - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetCryptographicallySecureRandomBytes")] + [LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetCryptographicallySecureRandomBytes")] internal static unsafe partial int GetCryptographicallySecureRandomBytes(byte* buffer, int length); } } From 3e34fd01a1cdf0b4c8069c97c81c32d069274f07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:31:52 +0000 Subject: [PATCH 10/10] Preserve secure wipe of ZipCrypto password material on non-browser platforms Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com> --- .../src/System.IO.Compression.csproj | 2 ++ .../ZipCryptoStream.ClearSensitiveData.Browser.cs | 12 ++++++++++++ .../ZipCryptoStream.ClearSensitiveData.Default.cs | 14 ++++++++++++++ .../src/System/IO/Compression/ZipCryptoStream.cs | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.ClearSensitiveData.Browser.cs create mode 100644 src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.ClearSensitiveData.Default.cs diff --git a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj index 9af63969bfaef5..4390030a64ad79 100644 --- a/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj +++ b/src/libraries/System.IO.Compression/src/System.IO.Compression.csproj @@ -69,6 +69,8 @@ + + diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.ClearSensitiveData.Browser.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.ClearSensitiveData.Browser.cs new file mode 100644 index 00000000000000..d689b2978e0f06 --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.ClearSensitiveData.Browser.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Compression +{ + internal sealed partial class ZipCryptoStream + { + // System.Security.Cryptography is not referenced on the browser platform, so + // CryptographicOperations.ZeroMemory is unavailable and Array.Clear is used instead. + private static void ClearSensitiveData(Span buffer) => buffer.Clear(); + } +} diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.ClearSensitiveData.Default.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.ClearSensitiveData.Default.cs new file mode 100644 index 00000000000000..5ba117b88a4ac5 --- /dev/null +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.ClearSensitiveData.Default.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography; + +namespace System.IO.Compression +{ + internal sealed partial class ZipCryptoStream + { + // Uses CryptographicOperations.ZeroMemory so the clear is not elided by the JIT, + // preserving the guarantee that password material is actually wiped from memory. + private static void ClearSensitiveData(Span buffer) => CryptographicOperations.ZeroMemory(buffer); + } +} diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs index 64450569184a34..3bca870a2e7148 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCryptoStream.cs @@ -137,7 +137,7 @@ internal static ZipCryptoKeys CreateKey(ReadOnlySpan password) } finally { - Array.Clear(passwordBytes); + ClearSensitiveData(passwordBytes); ArrayPool.Shared.Return(passwordBytes); }