< Summary

Line coverage
82%
Covered lines: 240
Uncovered lines: 50
Coverable lines: 290
Total lines: 495
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)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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)]
 112213        private static void ClearSensitiveData(Span<byte> buffer) => buffer.Clear();
 14    }
 15}
 16

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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
 13774446        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.
 37450        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
 37451        {
 37452            _base = baseStream;
 37453            _key0 = key0;
 37454            _key1 = key1;
 37455            _key2 = key2;
 37456            _encrypting = false;
 37457            _leaveOpen = leaveOpen;
 37458        }
 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
 37464        {
 37465            ArgumentNullException.ThrowIfNull(baseStream);
 37466            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 67
 37468            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedChec
 18769            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 18770        }
 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
 37476        {
 37477            ArgumentNullException.ThrowIfNull(baseStream);
 37478            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 79
 37480            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expecte
 18781            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 18782        }
 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)
 37493        {
 37494            ArgumentNullException.ThrowIfNull(baseStream);
 37495            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
 96
 37497            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
 37498        }
 99
 100        // Encryption constructor
 374101        private ZipCryptoStream(Stream baseStream,
 374102                               ZipCryptoKeys keys,
 374103                               ushort passwordVerifierLow2Bytes,
 374104                               uint? crc32,
 374105                               bool leaveOpen)
 374106        {
 374107            _base = baseStream;
 374108            _encrypting = true;
 374109            _leaveOpen = leaveOpen;
 374110            _verifierLow2Bytes = passwordVerifierLow2Bytes;
 374111            _crc32ForHeader = crc32;
 374112            _key0 = keys.Key0;
 374113            _key1 = keys.Key1;
 374114            _key2 = keys.Key2;
 374115        }
 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)
 1122120        {
 121            // Initialize keys with standard ZipCrypto initial values
 1122122            uint key0 = 305419896;
 1122123            uint key1 = 591751049;
 1122124            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.
 1122129            byte[] passwordBytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(password.Length));
 130            try
 1122131            {
 1122132                int byteCount = Encoding.UTF8.GetBytes(password, passwordBytes);
 36796133                for (int i = 0; i < byteCount; i++)
 17276134                {
 17276135                    UpdateKeys(ref key0, ref key1, ref key2, passwordBytes[i]);
 17276136                }
 1122137            }
 138            finally
 1122139            {
 1122140                ClearSensitiveData(passwordBytes);
 1122141                ArrayPool<byte>.Shared.Return(passwordBytes);
 1122142            }
 143
 1122144            return new ZipCryptoKeys(key0, key1, key2);
 1122145        }
 146
 147        private void CalculateHeader(Span<byte> header)
 374148        {
 374149            Debug.Assert(header.Length == 12);
 150
 151            // bytes 0..9 random
 374152            FillHeaderRandomBytes(header);
 153
 154            // bytes 10..11 verifier
 374155            if (_crc32ForHeader.HasValue)
 0156            {
 0157                uint crc = _crc32ForHeader.Value;
 0158                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
 0159            }
 160            else
 374161            {
 374162                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
 374163            }
 164
 165            // encrypt in place
 9724166            for (int i = 0; i < header.Length; i++)
 4488167            {
 4488168                byte p = header[i];
 4488169                byte ks = DecryptByte(_key2);
 4488170                header[i] = (byte)(p ^ ks);
 171
 172                // keys updated with PLAINTEXT per ZIP spec
 4488173                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 4488174            }
 374175        }
 176
 177        private unsafe void EnsureHeader()
 187178        {
 187179            if (!_encrypting || _headerWritten)
 0180            {
 0181                return;
 182            }
 183
 187184            Span<byte> header = stackalloc byte[12];
 187185            CalculateHeader(header);
 187186            _base.Write(header);
 187187            _headerWritten = true;
 187188        }
 189
 190        private async ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
 187191        {
 187192            if (!_encrypting || _headerWritten)
 0193            {
 0194                return;
 195            }
 196
 187197            byte[] header = new byte[12];
 187198            CalculateHeader(header);
 187199            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
 187200            _headerWritten = true;
 187201        }
 202
 203        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream base
 748204        {
 205            // Initialize keys from input
 748206            uint key0 = keys.Key0;
 748207            uint key1 = keys.Key1;
 748208            uint key2 = keys.Key2;
 209
 748210            byte[] hdr = new byte[12];
 211
 212            try
 748213            {
 748214                if (isAsync)
 374215                {
 374216                    await baseStream.ReadExactlyAsync(hdr, cancellationToken).ConfigureAwait(false);
 374217                }
 218                else
 374219                {
 374220                    baseStream.ReadExactly(hdr);
 374221                }
 748222            }
 0223            catch (EndOfStreamException)
 0224            {
 0225                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
 226            }
 227
 228            // Decrypt header and update keys
 19448229            for (int i = 0; i < hdr.Length; i++)
 8976230            {
 8976231                byte m = DecryptByte(key2);
 8976232                byte plain = (byte)(hdr[i] ^ m);
 8976233                UpdateKeys(ref key0, ref key1, ref key2, plain);
 8976234                hdr[i] = plain;
 8976235            }
 236
 748237            if (hdr[11] != expectedCheckByte)
 374238            {
 374239                throw new InvalidDataException(SR.InvalidPassword);
 240            }
 241
 374242            return (key0, key1, key2);
 374243        }
 244
 245        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
 68872246        {
 68872247            key0 = Crc32Update(key0, b);
 68872248            key1 += (key0 & 0xFF);
 68872249            key1 = key1 * 134775813 + 1;
 68872250            key2 = Crc32Update(key2, (byte)(key1 >> 24));
 68872251        }
 252
 253        private static byte DecryptByte(uint key2)
 51596254        {
 51596255            uint temp = key2 | 2;
 51596256            return (byte)((temp * (temp ^ 1)) >> 8);
 51596257        }
 258
 259        private byte DecryptAndUpdateKeys(byte ciph)
 19066260        {
 19066261            byte m = DecryptByte(_key2);
 19066262            byte plain = (byte)(ciph ^ m);
 19066263            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
 19066264            return plain;
 19066265        }
 266
 748267        public override bool CanRead => !_disposed && !_encrypting;
 374268        public override bool CanSeek => false;
 374269        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)
 187283        {
 187284            ValidateBufferArguments(buffer, offset, count);
 187285            return Read(buffer.AsSpan(offset, count));
 187286        }
 287
 288        public override int Read(Span<byte> destination)
 187289        {
 187290            ObjectDisposedException.ThrowIf(_disposed, this);
 187291            if (_encrypting)
 0292            {
 0293                throw new NotSupportedException(SR.ReadingNotSupported);
 294            }
 187295            int n = _base.Read(destination);
 19440296            for (int i = 0; i < n; i++)
 9533297                destination[i] = DecryptAndUpdateKeys(destination[i]);
 187298            return n;
 299
 187300        }
 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)
 187306        {
 187307            ValidateBufferArguments(buffer, offset, count);
 187308            Write(buffer.AsSpan(offset, count));
 187309        }
 310
 311        public override void Write(ReadOnlySpan<byte> buffer)
 187312        {
 187313            ObjectDisposedException.ThrowIf(_disposed, this);
 187314            if (!_encrypting)
 0315            {
 0316                throw new NotSupportedException(SR.WritingNotSupported);
 317            }
 318
 187319            EnsureHeader();
 320
 187321            byte[] workBuffer = GetWriteWorkBuffer();
 322
 374323            while (!buffer.IsEmpty)
 187324            {
 187325                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 326
 19440327                for (int i = 0; i < chunkSize; i++)
 9533328                {
 9533329                    byte ks = DecryptByte(_key2);
 9533330                    byte p = buffer[i];
 9533331                    workBuffer[i] = (byte)(p ^ ks);
 9533332                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 9533333                }
 334
 187335                _base.Write(workBuffer, 0, chunkSize);
 187336                buffer = buffer[chunkSize..];
 187337            }
 187338        }
 339
 340        protected override void Dispose(bool disposing)
 187341        {
 187342            if (_disposed)
 0343            {
 0344                return;
 345            }
 187346            _disposed = true;
 347
 187348            if (disposing)
 187349            {
 350                // If encrypted empty entry (no payload written), still must emit 12-byte header:
 187351                if (_encrypting)
 0352                {
 0353                    EnsureHeader();
 0354                }
 355
 187356                if (!_leaveOpen)
 187357                {
 187358                    _base.Dispose();
 187359                }
 187360            }
 187361            base.Dispose(disposing);
 187362        }
 363
 364        public override async ValueTask DisposeAsync()
 187365        {
 187366            if (_disposed)
 0367            {
 0368                return;
 369            }
 187370            _disposed = true;
 371
 372            // If encrypted empty entry (no payload written), still must emit 12-byte header:
 187373            if (_encrypting)
 0374            {
 0375                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
 0376            }
 187377            if (!_leaveOpen)
 187378            {
 187379                await _base.DisposeAsync().ConfigureAwait(false);
 187380            }
 381
 187382            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.
 187386        }
 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
 187395        {
 187396            ObjectDisposedException.ThrowIf(_disposed, this);
 187397            if (_encrypting)
 0398            {
 0399                throw new NotSupportedException(SR.ReadingNotSupported);
 400            }
 401
 187402            cancellationToken.ThrowIfCancellationRequested();
 187403            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 187404            Span<byte> span = buffer.Span;
 405
 19440406            for (int i = 0; i < n; i++)
 9533407            {
 9533408                span[i] = DecryptAndUpdateKeys(span[i]);
 9533409            }
 410
 187411            return n;
 187412        }
 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
 187421        {
 187422            ObjectDisposedException.ThrowIf(_disposed, this);
 187423            if (!_encrypting)
 0424            {
 0425                throw new NotSupportedException(SR.WritingNotSupported);
 426            }
 427
 187428            cancellationToken.ThrowIfCancellationRequested();
 429
 187430            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
 431
 187432            byte[] workBuffer = GetWriteWorkBuffer();
 433
 374434            while (!buffer.IsEmpty)
 187435            {
 187436                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 187437                ReadOnlySpan<byte> chunk = buffer.Span[..chunkSize];
 438
 19440439                for (int i = 0; i < chunkSize; i++)
 9533440                {
 9533441                    byte ks = DecryptByte(_key2);
 9533442                    byte p = chunk[i];
 9533443                    workBuffer[i] = (byte)(p ^ ks);
 9533444                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 9533445                }
 446
 187447                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
 187448                buffer = buffer[chunkSize..];
 187449            }
 187450        }
 451
 452        public override Task FlushAsync(CancellationToken cancellationToken)
 0453        {
 0454            ObjectDisposedException.ThrowIf(_disposed, this);
 0455            return _base.FlushAsync(cancellationToken);
 0456        }
 457
 374458        private byte[] GetWriteWorkBuffer() => _writeWorkBuffer ??= new byte[EncryptionBufferSize];
 459    }
 460}
 461

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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) =>
 37415            RandomNumberGenerator.Fill(header.Slice(0, 10));
 16    }
 17}
 18

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>)