< 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)]
 118213        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
 14439646        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.
 39450        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
 39451        {
 39452            _base = baseStream;
 39453            _key0 = key0;
 39454            _key1 = key1;
 39455            _key2 = key2;
 39456            _encrypting = false;
 39457            _leaveOpen = leaveOpen;
 39458        }
 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
 39464        {
 39465            ArgumentNullException.ThrowIfNull(baseStream);
 39466            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 67
 39468            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedChec
 19769            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 19770        }
 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
 39476        {
 39477            ArgumentNullException.ThrowIfNull(baseStream);
 39478            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 79
 39480            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expecte
 19781            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 19782        }
 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)
 39493        {
 39494            ArgumentNullException.ThrowIfNull(baseStream);
 39495            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
 96
 39497            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
 39498        }
 99
 100        // Encryption constructor
 394101        private ZipCryptoStream(Stream baseStream,
 394102                               ZipCryptoKeys keys,
 394103                               ushort passwordVerifierLow2Bytes,
 394104                               uint? crc32,
 394105                               bool leaveOpen)
 394106        {
 394107            _base = baseStream;
 394108            _encrypting = true;
 394109            _leaveOpen = leaveOpen;
 394110            _verifierLow2Bytes = passwordVerifierLow2Bytes;
 394111            _crc32ForHeader = crc32;
 394112            _key0 = keys.Key0;
 394113            _key1 = keys.Key1;
 394114            _key2 = keys.Key2;
 394115        }
 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)
 1182120        {
 121            // Initialize keys with standard ZipCrypto initial values
 1182122            uint key0 = 305419896;
 1182123            uint key1 = 591751049;
 1182124            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.
 1182129            byte[] passwordBytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(password.Length));
 130            try
 1182131            {
 1182132                int byteCount = Encoding.UTF8.GetBytes(password, passwordBytes);
 48296133                for (int i = 0; i < byteCount; i++)
 22966134                {
 22966135                    UpdateKeys(ref key0, ref key1, ref key2, passwordBytes[i]);
 22966136                }
 1182137            }
 138            finally
 1182139            {
 1182140                ClearSensitiveData(passwordBytes);
 1182141                ArrayPool<byte>.Shared.Return(passwordBytes);
 1182142            }
 143
 1182144            return new ZipCryptoKeys(key0, key1, key2);
 1182145        }
 146
 147        private void CalculateHeader(Span<byte> header)
 394148        {
 394149            Debug.Assert(header.Length == 12);
 150
 151            // bytes 0..9 random
 394152            FillHeaderRandomBytes(header);
 153
 154            // bytes 10..11 verifier
 394155            if (_crc32ForHeader.HasValue)
 0156            {
 0157                uint crc = _crc32ForHeader.Value;
 0158                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
 0159            }
 160            else
 394161            {
 394162                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
 394163            }
 164
 165            // encrypt in place
 10244166            for (int i = 0; i < header.Length; i++)
 4728167            {
 4728168                byte p = header[i];
 4728169                byte ks = DecryptByte(_key2);
 4728170                header[i] = (byte)(p ^ ks);
 171
 172                // keys updated with PLAINTEXT per ZIP spec
 4728173                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 4728174            }
 394175        }
 176
 177        private unsafe void EnsureHeader()
 197178        {
 197179            if (!_encrypting || _headerWritten)
 0180            {
 0181                return;
 182            }
 183
 197184            Span<byte> header = stackalloc byte[12];
 197185            CalculateHeader(header);
 197186            _base.Write(header);
 197187            _headerWritten = true;
 197188        }
 189
 190        private async ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
 197191        {
 197192            if (!_encrypting || _headerWritten)
 0193            {
 0194                return;
 195            }
 196
 197197            byte[] header = new byte[12];
 197198            CalculateHeader(header);
 197199            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
 197200            _headerWritten = true;
 197201        }
 202
 203        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream base
 788204        {
 205            // Initialize keys from input
 788206            uint key0 = keys.Key0;
 788207            uint key1 = keys.Key1;
 788208            uint key2 = keys.Key2;
 209
 788210            byte[] hdr = new byte[12];
 211
 212            try
 788213            {
 788214                if (isAsync)
 394215                {
 394216                    await baseStream.ReadExactlyAsync(hdr, cancellationToken).ConfigureAwait(false);
 394217                }
 218                else
 394219                {
 394220                    baseStream.ReadExactly(hdr);
 394221                }
 788222            }
 0223            catch (EndOfStreamException)
 0224            {
 0225                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
 226            }
 227
 228            // Decrypt header and update keys
 20488229            for (int i = 0; i < hdr.Length; i++)
 9456230            {
 9456231                byte m = DecryptByte(key2);
 9456232                byte plain = (byte)(hdr[i] ^ m);
 9456233                UpdateKeys(ref key0, ref key1, ref key2, plain);
 9456234                hdr[i] = plain;
 9456235            }
 236
 788237            if (hdr[11] != expectedCheckByte)
 394238            {
 394239                throw new InvalidDataException(SR.InvalidPassword);
 240            }
 241
 394242            return (key0, key1, key2);
 394243        }
 244
 245        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
 72198246        {
 72198247            key0 = Crc32Update(key0, b);
 72198248            key1 += (key0 & 0xFF);
 72198249            key1 = key1 * 134775813 + 1;
 72198250            key2 = Crc32Update(key2, (byte)(key1 >> 24));
 72198251        }
 252
 253        private static byte DecryptByte(uint key2)
 49232254        {
 49232255            uint temp = key2 | 2;
 49232256            return (byte)((temp * (temp ^ 1)) >> 8);
 49232257        }
 258
 259        private byte DecryptAndUpdateKeys(byte ciph)
 17524260        {
 17524261            byte m = DecryptByte(_key2);
 17524262            byte plain = (byte)(ciph ^ m);
 17524263            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
 17524264            return plain;
 17524265        }
 266
 788267        public override bool CanRead => !_disposed && !_encrypting;
 394268        public override bool CanSeek => false;
 394269        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)
 197283        {
 197284            ValidateBufferArguments(buffer, offset, count);
 197285            return Read(buffer.AsSpan(offset, count));
 197286        }
 287
 288        public override int Read(Span<byte> destination)
 197289        {
 197290            ObjectDisposedException.ThrowIf(_disposed, this);
 197291            if (_encrypting)
 0292            {
 0293                throw new NotSupportedException(SR.ReadingNotSupported);
 294            }
 197295            int n = _base.Read(destination);
 17918296            for (int i = 0; i < n; i++)
 8762297                destination[i] = DecryptAndUpdateKeys(destination[i]);
 197298            return n;
 299
 197300        }
 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)
 197306        {
 197307            ValidateBufferArguments(buffer, offset, count);
 197308            Write(buffer.AsSpan(offset, count));
 197309        }
 310
 311        public override void Write(ReadOnlySpan<byte> buffer)
 197312        {
 197313            ObjectDisposedException.ThrowIf(_disposed, this);
 197314            if (!_encrypting)
 0315            {
 0316                throw new NotSupportedException(SR.WritingNotSupported);
 317            }
 318
 197319            EnsureHeader();
 320
 197321            byte[] workBuffer = GetWriteWorkBuffer();
 322
 394323            while (!buffer.IsEmpty)
 197324            {
 197325                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 326
 17918327                for (int i = 0; i < chunkSize; i++)
 8762328                {
 8762329                    byte ks = DecryptByte(_key2);
 8762330                    byte p = buffer[i];
 8762331                    workBuffer[i] = (byte)(p ^ ks);
 8762332                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 8762333                }
 334
 197335                _base.Write(workBuffer, 0, chunkSize);
 197336                buffer = buffer[chunkSize..];
 197337            }
 197338        }
 339
 340        protected override void Dispose(bool disposing)
 197341        {
 197342            if (_disposed)
 0343            {
 0344                return;
 345            }
 197346            _disposed = true;
 347
 197348            if (disposing)
 197349            {
 350                // If encrypted empty entry (no payload written), still must emit 12-byte header:
 197351                if (_encrypting)
 0352                {
 0353                    EnsureHeader();
 0354                }
 355
 197356                if (!_leaveOpen)
 197357                {
 197358                    _base.Dispose();
 197359                }
 197360            }
 197361            base.Dispose(disposing);
 197362        }
 363
 364        public override async ValueTask DisposeAsync()
 197365        {
 197366            if (_disposed)
 0367            {
 0368                return;
 369            }
 197370            _disposed = true;
 371
 372            // If encrypted empty entry (no payload written), still must emit 12-byte header:
 197373            if (_encrypting)
 0374            {
 0375                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
 0376            }
 197377            if (!_leaveOpen)
 197378            {
 197379                await _base.DisposeAsync().ConfigureAwait(false);
 197380            }
 381
 197382            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.
 197386        }
 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
 197395        {
 197396            ObjectDisposedException.ThrowIf(_disposed, this);
 197397            if (_encrypting)
 0398            {
 0399                throw new NotSupportedException(SR.ReadingNotSupported);
 400            }
 401
 197402            cancellationToken.ThrowIfCancellationRequested();
 197403            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 197404            Span<byte> span = buffer.Span;
 405
 17918406            for (int i = 0; i < n; i++)
 8762407            {
 8762408                span[i] = DecryptAndUpdateKeys(span[i]);
 8762409            }
 410
 197411            return n;
 197412        }
 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
 197421        {
 197422            ObjectDisposedException.ThrowIf(_disposed, this);
 197423            if (!_encrypting)
 0424            {
 0425                throw new NotSupportedException(SR.WritingNotSupported);
 426            }
 427
 197428            cancellationToken.ThrowIfCancellationRequested();
 429
 197430            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
 431
 197432            byte[] workBuffer = GetWriteWorkBuffer();
 433
 394434            while (!buffer.IsEmpty)
 197435            {
 197436                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 197437                ReadOnlySpan<byte> chunk = buffer.Span[..chunkSize];
 438
 17918439                for (int i = 0; i < chunkSize; i++)
 8762440                {
 8762441                    byte ks = DecryptByte(_key2);
 8762442                    byte p = chunk[i];
 8762443                    workBuffer[i] = (byte)(p ^ ks);
 8762444                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 8762445                }
 446
 197447                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
 197448                buffer = buffer[chunkSize..];
 197449            }
 197450        }
 451
 452        public override Task FlushAsync(CancellationToken cancellationToken)
 0453        {
 0454            ObjectDisposedException.ThrowIf(_disposed, this);
 0455            return _base.FlushAsync(cancellationToken);
 0456        }
 457
 394458        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) =>
 39415            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>)