< Summary

Line coverage
82%
Covered lines: 240
Uncovered lines: 50
Coverable lines: 290
Total lines: 492
Line coverage: 82.7%
Branch coverage
71%
Covered branches: 47
Total branches: 66
Branch coverage: 71.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipCryptoStream.ClearSensitiveData.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Runtime.CompilerServices;
 5
 6namespace System.IO.Compression
 7{
 8    internal sealed partial class ZipCryptoStream
 9    {
 10        // NoOptimize to prevent the optimizer from deciding this call is unnecessary.
 11        // NoInlining to prevent the inliner from forgetting that the method was no-optimize.
 12        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
 40213        private static void ClearSensitiveData(Span<byte> buffer) => buffer.Clear();
 14    }
 15}

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipCryptoStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Buffers.Binary;
 6using System.Diagnostics;
 7using System.Text;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11namespace System.IO.Compression
 12{
 13    internal sealed partial class ZipCryptoStream : Stream
 14    {
 15        private const int EncryptionBufferSize = 4096;
 16
 17        private readonly bool _encrypting;
 18        private readonly Stream _base;
 19        private readonly bool _leaveOpen;
 20        private bool _headerWritten;
 21        private bool _disposed;
 22        private readonly ushort _verifierLow2Bytes;       // (DOS time low word when streaming)
 23        private readonly uint? _crc32ForHeader;           // (CRC-based header when not streaming)
 24
 25        private uint _key0;
 26        private uint _key1;
 27        private uint _key2;
 128        private static readonly uint[] s_crc2Table = CreateCrc32Table();
 29
 30        // Reusable work buffer for write operations, lazily allocated on first write
 31        private byte[]? _writeWorkBuffer;
 32
 33        private static uint[] CreateCrc32Table()
 134        {
 135            var table = new uint[256];
 51436            for (uint i = 0; i < 256; i++)
 25637            {
 25638                uint c = i;
 460839                for (int j = 0; j < 8; j++)
 204840                    c = (c & 1) != 0 ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
 25641                table[i] = c;
 25642            }
 143            return table;
 144        }
 45
 2868446        private static uint Crc32Update(uint crc, byte b) => s_crc2Table[(crc ^ b) & 0xFF] ^ (crc >> 8);
 47
 48        // Private decryption constructor - use Create/CreateAsync factory methods instead.
 49        // Keys must already be validated before calling this constructor.
 13450        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
 13451        {
 13452            _base = baseStream;
 13453            _key0 = key0;
 13454            _key1 = key1;
 13455            _key2 = key2;
 13456            _encrypting = false;
 13457            _leaveOpen = leaveOpen;
 13458        }
 59
 60        /// <summary>
 61        /// Creates a ZipCryptoStream for decryption. Reads and validates the 12-byte header synchronously.
 62        /// </summary>
 63        internal static ZipCryptoStream Create(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encry
 13464        {
 13465            ArgumentNullException.ThrowIfNull(baseStream);
 13466            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 67
 13468            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedChec
 6769            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 6770        }
 71
 72        /// <summary>
 73        /// Creates a ZipCryptoStream for decryption. Reads and validates the 12-byte header asynchronously.
 74        /// </summary>
 75        internal static async Task<ZipCryptoStream> CreateAsync(Stream baseStream, ZipCryptoKeys keys, byte expectedChec
 13476        {
 13477            ArgumentNullException.ThrowIfNull(baseStream);
 13478            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 79
 13480            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expecte
 6781            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 6782        }
 83
 84        /// <summary>
 85        /// Creates a ZipCryptoStream for encryption. Only synchronous creation is needed since no I/O is performed here
 86        /// </summary>
 87        internal static ZipCryptoStream Create(Stream baseStream,
 88                                             ZipCryptoKeys keys,
 89                                             ushort passwordVerifierLow2Bytes,
 90                                             bool encrypting,
 91                                             uint? crc32 = null,
 92                                             bool leaveOpen = false)
 13493        {
 13494            ArgumentNullException.ThrowIfNull(baseStream);
 13495            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
 96
 13497            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
 13498        }
 99
 100        // Encryption constructor
 134101        private ZipCryptoStream(Stream baseStream,
 134102                               ZipCryptoKeys keys,
 134103                               ushort passwordVerifierLow2Bytes,
 134104                               uint? crc32,
 134105                               bool leaveOpen)
 134106        {
 134107            _base = baseStream;
 134108            _encrypting = true;
 134109            _leaveOpen = leaveOpen;
 134110            _verifierLow2Bytes = passwordVerifierLow2Bytes;
 134111            _crc32ForHeader = crc32;
 134112            _key0 = keys.Key0;
 134113            _key1 = keys.Key1;
 134114            _key2 = keys.Key2;
 134115        }
 116
 117        // Creates the persisted key material from a password.
 118        // Returns a struct of 3 integers to keep the key off the heap.
 119        internal static ZipCryptoKeys CreateKey(ReadOnlySpan<char> password)
 402120        {
 121            // Initialize keys with standard ZipCrypto initial values
 402122            uint key0 = 305419896;
 402123            uint key1 = 591751049;
 402124            uint key2 = 878082192;
 125
 126            // Feed the password's UTF-8 bytes into the key schedule. UTF-8 (rather than ASCII)
 127            // preserves non-ASCII passwords; interop with tools that assume a different code page
 128            // is documented as a caveat.
 402129            byte[] passwordBytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(password.Length));
 130            try
 402131            {
 402132                int byteCount = Encoding.UTF8.GetBytes(password, passwordBytes);
 12088133                for (int i = 0; i < byteCount; i++)
 5642134                {
 5642135                    UpdateKeys(ref key0, ref key1, ref key2, passwordBytes[i]);
 5642136                }
 402137            }
 138            finally
 402139            {
 402140                ClearSensitiveData(passwordBytes);
 402141                ArrayPool<byte>.Shared.Return(passwordBytes);
 402142            }
 143
 402144            return new ZipCryptoKeys(key0, key1, key2);
 402145        }
 146
 147        private void CalculateHeader(Span<byte> header)
 134148        {
 134149            Debug.Assert(header.Length == 12);
 150
 151            // bytes 0..9 random
 134152            FillHeaderRandomBytes(header);
 153
 154            // bytes 10..11 verifier
 134155            if (_crc32ForHeader.HasValue)
 0156            {
 0157                uint crc = _crc32ForHeader.Value;
 0158                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
 0159            }
 160            else
 134161            {
 134162                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
 134163            }
 164
 165            // encrypt in place
 3484166            for (int i = 0; i < header.Length; i++)
 1608167            {
 1608168                byte p = header[i];
 1608169                byte ks = DecryptByte(_key2);
 1608170                header[i] = (byte)(p ^ ks);
 171
 172                // keys updated with PLAINTEXT per ZIP spec
 1608173                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 1608174            }
 134175        }
 176
 177        private unsafe void EnsureHeader()
 67178        {
 67179            if (!_encrypting || _headerWritten)
 0180            {
 0181                return;
 182            }
 183
 67184            Span<byte> header = stackalloc byte[12];
 67185            CalculateHeader(header);
 67186            _base.Write(header);
 67187            _headerWritten = true;
 67188        }
 189
 190        private async ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
 67191        {
 67192            if (!_encrypting || _headerWritten)
 0193            {
 0194                return;
 195            }
 196
 67197            byte[] header = new byte[12];
 67198            CalculateHeader(header);
 67199            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
 67200            _headerWritten = true;
 67201        }
 202
 203        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream base
 268204        {
 205            // Initialize keys from input
 268206            uint key0 = keys.Key0;
 268207            uint key1 = keys.Key1;
 268208            uint key2 = keys.Key2;
 209
 268210            byte[] hdr = new byte[12];
 211
 212            try
 268213            {
 268214                if (isAsync)
 134215                {
 134216                    await baseStream.ReadExactlyAsync(hdr, cancellationToken).ConfigureAwait(false);
 134217                }
 218                else
 134219                {
 134220                    baseStream.ReadExactly(hdr);
 134221                }
 268222            }
 0223            catch (EndOfStreamException)
 0224            {
 0225                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
 226            }
 227
 228            // Decrypt header and update keys
 6968229            for (int i = 0; i < hdr.Length; i++)
 3216230            {
 3216231                byte m = DecryptByte(key2);
 3216232                byte plain = (byte)(hdr[i] ^ m);
 3216233                UpdateKeys(ref key0, ref key1, ref key2, plain);
 3216234                hdr[i] = plain;
 3216235            }
 236
 268237            if (hdr[11] != expectedCheckByte)
 134238            {
 134239                throw new InvalidDataException(SR.InvalidPassword);
 240            }
 241
 134242            return (key0, key1, key2);
 134243        }
 244
 245        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
 14342246        {
 14342247            key0 = Crc32Update(key0, b);
 14342248            key1 += (key0 & 0xFF);
 14342249            key1 = key1 * 134775813 + 1;
 14342250            key2 = Crc32Update(key2, (byte)(key1 >> 24));
 14342251        }
 252
 253        private static byte DecryptByte(uint key2)
 8700254        {
 8700255            uint temp = key2 | 2;
 8700256            return (byte)((temp * (temp ^ 1)) >> 8);
 8700257        }
 258
 259        private byte DecryptAndUpdateKeys(byte ciph)
 1938260        {
 1938261            byte m = DecryptByte(_key2);
 1938262            byte plain = (byte)(ciph ^ m);
 1938263            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
 1938264            return plain;
 1938265        }
 266
 268267        public override bool CanRead => !_disposed && !_encrypting;
 134268        public override bool CanSeek => false;
 134269        public override bool CanWrite => !_disposed && _encrypting;
 0270        public override long Length => throw new NotSupportedException();
 271        public override long Position
 272        {
 0273            get => throw new NotSupportedException();
 0274            set => throw new NotSupportedException();
 275        }
 276        public override void Flush()
 0277        {
 0278            ObjectDisposedException.ThrowIf(_disposed, this);
 0279            _base.Flush();
 0280        }
 281
 282        public override int Read(byte[] buffer, int offset, int count)
 67283        {
 67284            ValidateBufferArguments(buffer, offset, count);
 67285            return Read(buffer.AsSpan(offset, count));
 67286        }
 287
 288        public override int Read(Span<byte> destination)
 67289        {
 67290            ObjectDisposedException.ThrowIf(_disposed, this);
 67291            if (_encrypting)
 0292            {
 0293                throw new NotSupportedException(SR.ReadingNotSupported);
 294            }
 67295            int n = _base.Read(destination);
 2072296            for (int i = 0; i < n; i++)
 969297                destination[i] = DecryptAndUpdateKeys(destination[i]);
 67298            return n;
 299
 67300        }
 301
 0302        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 0303        public override void SetLength(long value) => throw new NotSupportedException();
 304
 305        public override void Write(byte[] buffer, int offset, int count)
 67306        {
 67307            ValidateBufferArguments(buffer, offset, count);
 67308            Write(buffer.AsSpan(offset, count));
 67309        }
 310
 311        public override void Write(ReadOnlySpan<byte> buffer)
 67312        {
 67313            ObjectDisposedException.ThrowIf(_disposed, this);
 67314            if (!_encrypting)
 0315            {
 0316                throw new NotSupportedException(SR.WritingNotSupported);
 317            }
 318
 67319            EnsureHeader();
 320
 67321            byte[] workBuffer = GetWriteWorkBuffer();
 322
 134323            while (!buffer.IsEmpty)
 67324            {
 67325                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 326
 2072327                for (int i = 0; i < chunkSize; i++)
 969328                {
 969329                    byte ks = DecryptByte(_key2);
 969330                    byte p = buffer[i];
 969331                    workBuffer[i] = (byte)(p ^ ks);
 969332                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 969333                }
 334
 67335                _base.Write(workBuffer, 0, chunkSize);
 67336                buffer = buffer[chunkSize..];
 67337            }
 67338        }
 339
 340        protected override void Dispose(bool disposing)
 67341        {
 67342            if (_disposed)
 0343            {
 0344                return;
 345            }
 67346            _disposed = true;
 347
 67348            if (disposing)
 67349            {
 350                // If encrypted empty entry (no payload written), still must emit 12-byte header:
 67351                if (_encrypting)
 0352                {
 0353                    EnsureHeader();
 0354                }
 355
 67356                if (!_leaveOpen)
 67357                {
 67358                    _base.Dispose();
 67359                }
 67360            }
 67361            base.Dispose(disposing);
 67362        }
 363
 364        public override async ValueTask DisposeAsync()
 67365        {
 67366            if (_disposed)
 0367            {
 0368                return;
 369            }
 67370            _disposed = true;
 371
 372            // If encrypted empty entry (no payload written), still must emit 12-byte header:
 67373            if (_encrypting)
 0374            {
 0375                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
 0376            }
 67377            if (!_leaveOpen)
 67378            {
 67379                await _base.DisposeAsync().ConfigureAwait(false);
 67380            }
 381
 67382            GC.SuppressFinalize(this);
 383
 384            // Don't call base.DisposeAsync() as it would call Dispose() synchronously,
 385            // which could fail on async-only streams. We've already handled all cleanup.
 67386        }
 387
 388        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0389        {
 0390            ValidateBufferArguments(buffer, offset, count);
 0391            return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
 0392        }
 393
 394        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = defaul
 67395        {
 67396            ObjectDisposedException.ThrowIf(_disposed, this);
 67397            if (_encrypting)
 0398            {
 0399                throw new NotSupportedException(SR.ReadingNotSupported);
 400            }
 401
 67402            cancellationToken.ThrowIfCancellationRequested();
 67403            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 67404            Span<byte> span = buffer.Span;
 405
 2072406            for (int i = 0; i < n; i++)
 969407            {
 969408                span[i] = DecryptAndUpdateKeys(span[i]);
 969409            }
 410
 67411            return n;
 67412        }
 413
 414        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0415        {
 0416            ValidateBufferArguments(buffer, offset, count);
 0417            return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
 0418        }
 419
 420        public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = de
 67421        {
 67422            ObjectDisposedException.ThrowIf(_disposed, this);
 67423            if (!_encrypting)
 0424            {
 0425                throw new NotSupportedException(SR.WritingNotSupported);
 426            }
 427
 67428            cancellationToken.ThrowIfCancellationRequested();
 429
 67430            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
 431
 67432            byte[] workBuffer = GetWriteWorkBuffer();
 433
 134434            while (!buffer.IsEmpty)
 67435            {
 67436                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 67437                ReadOnlySpan<byte> chunk = buffer.Span[..chunkSize];
 438
 2072439                for (int i = 0; i < chunkSize; i++)
 969440                {
 969441                    byte ks = DecryptByte(_key2);
 969442                    byte p = chunk[i];
 969443                    workBuffer[i] = (byte)(p ^ ks);
 969444                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 969445                }
 446
 67447                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
 67448                buffer = buffer[chunkSize..];
 67449            }
 67450        }
 451
 452        public override Task FlushAsync(CancellationToken cancellationToken)
 0453        {
 0454            ObjectDisposedException.ThrowIf(_disposed, this);
 0455            return _base.FlushAsync(cancellationToken);
 0456        }
 457
 134458        private byte[] GetWriteWorkBuffer() => _writeWorkBuffer ??= new byte[EncryptionBufferSize];
 459    }
 460}

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipCryptoStream.Random.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Security.Cryptography;
 5
 6namespace System.IO.Compression
 7{
 8    internal sealed partial class ZipCryptoStream
 9    {
 10        // Everywhere except browser and wasi, System.IO.Compression already references
 11        // System.Security.Cryptography because WinZip AES needs it, so the ZipCrypto header salt
 12        // is filled from RandomNumberGenerator. See ZipCryptoStream.Random.BrowserOrWasi.cs for
 13        // the interop-based implementation used there and for why the split exists.
 14        private static void FillHeaderRandomBytes(Span<byte> header) =>
 13415            RandomNumberGenerator.Fill(header.Slice(0, 10));
 16    }
 17}

Methods/Properties

ClearSensitiveData(System.Span`1<System.Byte>)
.cctor()
CreateCrc32Table()
Crc32Update(System.UInt32,System.Byte)
.ctor(System.IO.Stream,System.UInt32,System.UInt32,System.UInt32,System.Boolean)
Create(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Boolean,System.Boolean)
CreateAsync(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Boolean,System.Threading.CancellationToken,System.Boolean)
Create(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.UInt16,System.Boolean,System.Nullable`1<System.UInt32>,System.Boolean)
.ctor(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.UInt16,System.Nullable`1<System.UInt32>,System.Boolean)
CreateKey(System.ReadOnlySpan`1<System.Char>)
CalculateHeader(System.Span`1<System.Byte>)
EnsureHeader()
EnsureHeaderAsync(System.Threading.CancellationToken)
ReadAndValidateHeaderCore(System.Boolean,System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Threading.CancellationToken)
UpdateKeys(System.UInt32&,System.UInt32&,System.UInt32&,System.Byte)
DecryptByte(System.UInt32)
DecryptAndUpdateKeys(System.Byte)
CanRead()
CanSeek()
CanWrite()
Length()
Position()
Position(System.Int64)
Flush()
Read(System.Byte[],System.Int32,System.Int32)
Read(System.Span`1<System.Byte>)
Seek(System.Int64,System.IO.SeekOrigin)
SetLength(System.Int64)
Write(System.Byte[],System.Int32,System.Int32)
Write(System.ReadOnlySpan`1<System.Byte>)
Dispose(System.Boolean)
DisposeAsync()
ReadAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
WriteAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
FlushAsync(System.Threading.CancellationToken)
GetWriteWorkBuffer()
FillHeaderRandomBytes(System.Span`1<System.Byte>)