mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-11-15 17:02:03 +00:00
Validate 7z AES KDF limits before unsalted key derivation
This commit is contained in:
@@ -486,6 +486,8 @@ using (var archive = ZipArchive.OpenArchive("file.zip"))
|
||||
|
||||
`CheckCrc` validates archive-level payload checksums when the format stores reliable metadata, such as ZIP CRC32 values. Formats without payload checksums skip this validation. Decompressor integrity checks that are required to decode a stream may still fail even when `CheckCrc` is disabled.
|
||||
|
||||
7z AES key derivation accepts iteration exponents 0 through 24 and the special `0x3F` direct-key mode. Larger iteration exponents throw `NotSupportedException` before key derivation, including when salt and IV are absent.
|
||||
|
||||
When using `SymbolicLinkHandler`, directory extraction rejects link targets outside the extraction root and never follows symbolic links or reparse points while extracting later entries. The handler itself remains trusted application code.
|
||||
|
||||
### Options matrix
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed partial class AesDecoderStream : DecoderStream2
|
||||
public AesDecoderStream(Stream input, byte[] info, IPasswordProvider pass, long limit)
|
||||
{
|
||||
var password = pass.CryptoGetTextPassword();
|
||||
if (password == null)
|
||||
if (password is null)
|
||||
{
|
||||
throw new SharpCompress.Common.CryptographicException(
|
||||
"Encrypted 7Zip archive has no password specified."
|
||||
@@ -43,7 +43,7 @@ internal sealed partial class AesDecoderStream : DecoderStream2
|
||||
|
||||
var passwordBytes = Encoding.Unicode.GetBytes(password);
|
||||
var key = InitKey(numCyclesPower, salt, passwordBytes);
|
||||
if (key == null)
|
||||
if (key is null)
|
||||
{
|
||||
throw new ArchiveOperationException("Initialized with null key");
|
||||
}
|
||||
@@ -147,13 +147,24 @@ internal sealed partial class AesDecoderStream : DecoderStream2
|
||||
|
||||
private void Init(byte[] info, out int numCyclesPower, out byte[] salt, out byte[] iv)
|
||||
{
|
||||
if (info.Length == 0)
|
||||
{
|
||||
throw new InvalidFormatException("Missing 7z AES properties.");
|
||||
}
|
||||
var bt = info[0];
|
||||
numCyclesPower = bt & 0x3F;
|
||||
|
||||
// Check before the no-salt/no-IV return, and before starting expensive key derivation.
|
||||
// 0x3F is the format's direct-key mode, not an iteration exponent.
|
||||
if (numCyclesPower > 24 && numCyclesPower != 0x3F)
|
||||
{
|
||||
throw new NotSupportedException("Unsupported 7z AES KDF count.");
|
||||
}
|
||||
|
||||
if ((bt & 0xC0) == 0)
|
||||
{
|
||||
salt = Array.Empty<byte>();
|
||||
iv = Array.Empty<byte>();
|
||||
iv = new byte[16];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,11 +194,6 @@ internal sealed partial class AesDecoderStream : DecoderStream2
|
||||
{
|
||||
iv[i] = info[i + saltSize + 2];
|
||||
}
|
||||
|
||||
if (numCyclesPower > 24)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[]? InitKey(int mNumCyclesPower, byte[] salt, byte[] pass)
|
||||
|
||||
35
tests/SharpCompress.Test/SevenZip/SevenZipAesKdfTests.cs
Normal file
35
tests/SharpCompress.Test/SevenZip/SevenZipAesKdfTests.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using SharpCompress.Compressors.LZMA.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.SevenZip;
|
||||
|
||||
public class SevenZipAesKdfTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(25)]
|
||||
[InlineData(31)]
|
||||
[InlineData(62)]
|
||||
public void SevenZipAes_Rejects_Excessive_Kdf_Without_Salt(int exponent)
|
||||
{
|
||||
using var input = new MemoryStream();
|
||||
Assert.Throws<NotSupportedException>(() =>
|
||||
new AesDecoderStream(input, [(byte)exponent], new Password(), 0)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SevenZipAes_Allows_Direct_Key_Without_Iv()
|
||||
{
|
||||
using var input = new MemoryStream();
|
||||
using var decoder = new AesDecoderStream(input, [0x3F], new Password(), 0);
|
||||
Assert.Equal(0, decoder.Read(new byte[1], 0, 1));
|
||||
}
|
||||
|
||||
private sealed class Password : IPasswordProvider
|
||||
{
|
||||
public string CryptoGetTextPassword() => "test";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user