< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 290
Coverable lines: 290
Total lines: 495
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 66
Branch coverage: 0%
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)]
 013        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;
 028        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()
 034        {
 035            var table = new uint[256];
 036            for (uint i = 0; i < 256; i++)
 037            {
 038                uint c = i;
 039                for (int j = 0; j < 8; j++)
 040                    c = (c & 1) != 0 ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
 041                table[i] = c;
 042            }
 043            return table;
 044        }
 45
 046        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.
 050        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
 051        {
 052            _base = baseStream;
 053            _key0 = key0;
 054            _key1 = key1;
 055            _key2 = key2;
 056            _encrypting = false;
 057            _leaveOpen = leaveOpen;
 058        }
 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
 064        {
 065            ArgumentNullException.ThrowIfNull(baseStream);
 066            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 67
 068            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedChec
 069            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 070        }
 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
 076        {
 077            ArgumentNullException.ThrowIfNull(baseStream);
 078            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 79
 080            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expecte
 081            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 082        }
 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)
 093        {
 094            ArgumentNullException.ThrowIfNull(baseStream);
 095            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
 96
 097            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
 098        }
 99
 100        // Encryption constructor
 0101        private ZipCryptoStream(Stream baseStream,
 0102                               ZipCryptoKeys keys,
 0103                               ushort passwordVerifierLow2Bytes,
 0104                               uint? crc32,
 0105                               bool leaveOpen)
 0106        {
 0107            _base = baseStream;
 0108            _encrypting = true;
 0109            _leaveOpen = leaveOpen;
 0110            _verifierLow2Bytes = passwordVerifierLow2Bytes;
 0111            _crc32ForHeader = crc32;
 0112            _key0 = keys.Key0;
 0113            _key1 = keys.Key1;
 0114            _key2 = keys.Key2;
 0115        }
 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)
 0120        {
 121            // Initialize keys with standard ZipCrypto initial values
 0122            uint key0 = 305419896;
 0123            uint key1 = 591751049;
 0124            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.
 0129            byte[] passwordBytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(password.Length));
 130            try
 0131            {
 0132                int byteCount = Encoding.UTF8.GetBytes(password, passwordBytes);
 0133                for (int i = 0; i < byteCount; i++)
 0134                {
 0135                    UpdateKeys(ref key0, ref key1, ref key2, passwordBytes[i]);
 0136                }
 0137            }
 138            finally
 0139            {
 0140                ClearSensitiveData(passwordBytes);
 0141                ArrayPool<byte>.Shared.Return(passwordBytes);
 0142            }
 143
 0144            return new ZipCryptoKeys(key0, key1, key2);
 0145        }
 146
 147        private void CalculateHeader(Span<byte> header)
 0148        {
 0149            Debug.Assert(header.Length == 12);
 150
 151            // bytes 0..9 random
 0152            FillHeaderRandomBytes(header);
 153
 154            // bytes 10..11 verifier
 0155            if (_crc32ForHeader.HasValue)
 0156            {
 0157                uint crc = _crc32ForHeader.Value;
 0158                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
 0159            }
 160            else
 0161            {
 0162                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
 0163            }
 164
 165            // encrypt in place
 0166            for (int i = 0; i < header.Length; i++)
 0167            {
 0168                byte p = header[i];
 0169                byte ks = DecryptByte(_key2);
 0170                header[i] = (byte)(p ^ ks);
 171
 172                // keys updated with PLAINTEXT per ZIP spec
 0173                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 0174            }
 0175        }
 176
 177        private unsafe void EnsureHeader()
 0178        {
 0179            if (!_encrypting || _headerWritten)
 0180            {
 0181                return;
 182            }
 183
 0184            Span<byte> header = stackalloc byte[12];
 0185            CalculateHeader(header);
 0186            _base.Write(header);
 0187            _headerWritten = true;
 0188        }
 189
 190        private async ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
 0191        {
 0192            if (!_encrypting || _headerWritten)
 0193            {
 0194                return;
 195            }
 196
 0197            byte[] header = new byte[12];
 0198            CalculateHeader(header);
 0199            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
 0200            _headerWritten = true;
 0201        }
 202
 203        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream base
 0204        {
 205            // Initialize keys from input
 0206            uint key0 = keys.Key0;
 0207            uint key1 = keys.Key1;
 0208            uint key2 = keys.Key2;
 209
 0210            byte[] hdr = new byte[12];
 211
 212            try
 0213            {
 0214                if (isAsync)
 0215                {
 0216                    await baseStream.ReadExactlyAsync(hdr, cancellationToken).ConfigureAwait(false);
 0217                }
 218                else
 0219                {
 0220                    baseStream.ReadExactly(hdr);
 0221                }
 0222            }
 0223            catch (EndOfStreamException)
 0224            {
 0225                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
 226            }
 227
 228            // Decrypt header and update keys
 0229            for (int i = 0; i < hdr.Length; i++)
 0230            {
 0231                byte m = DecryptByte(key2);
 0232                byte plain = (byte)(hdr[i] ^ m);
 0233                UpdateKeys(ref key0, ref key1, ref key2, plain);
 0234                hdr[i] = plain;
 0235            }
 236
 0237            if (hdr[11] != expectedCheckByte)
 0238            {
 0239                throw new InvalidDataException(SR.InvalidPassword);
 240            }
 241
 0242            return (key0, key1, key2);
 0243        }
 244
 245        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
 0246        {
 0247            key0 = Crc32Update(key0, b);
 0248            key1 += (key0 & 0xFF);
 0249            key1 = key1 * 134775813 + 1;
 0250            key2 = Crc32Update(key2, (byte)(key1 >> 24));
 0251        }
 252
 253        private static byte DecryptByte(uint key2)
 0254        {
 0255            uint temp = key2 | 2;
 0256            return (byte)((temp * (temp ^ 1)) >> 8);
 0257        }
 258
 259        private byte DecryptAndUpdateKeys(byte ciph)
 0260        {
 0261            byte m = DecryptByte(_key2);
 0262            byte plain = (byte)(ciph ^ m);
 0263            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
 0264            return plain;
 0265        }
 266
 0267        public override bool CanRead => !_disposed && !_encrypting;
 0268        public override bool CanSeek => false;
 0269        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)
 0283        {
 0284            ValidateBufferArguments(buffer, offset, count);
 0285            return Read(buffer.AsSpan(offset, count));
 0286        }
 287
 288        public override int Read(Span<byte> destination)
 0289        {
 0290            ObjectDisposedException.ThrowIf(_disposed, this);
 0291            if (_encrypting)
 0292            {
 0293                throw new NotSupportedException(SR.ReadingNotSupported);
 294            }
 0295            int n = _base.Read(destination);
 0296            for (int i = 0; i < n; i++)
 0297                destination[i] = DecryptAndUpdateKeys(destination[i]);
 0298            return n;
 299
 0300        }
 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)
 0306        {
 0307            ValidateBufferArguments(buffer, offset, count);
 0308            Write(buffer.AsSpan(offset, count));
 0309        }
 310
 311        public override void Write(ReadOnlySpan<byte> buffer)
 0312        {
 0313            ObjectDisposedException.ThrowIf(_disposed, this);
 0314            if (!_encrypting)
 0315            {
 0316                throw new NotSupportedException(SR.WritingNotSupported);
 317            }
 318
 0319            EnsureHeader();
 320
 0321            byte[] workBuffer = GetWriteWorkBuffer();
 322
 0323            while (!buffer.IsEmpty)
 0324            {
 0325                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 326
 0327                for (int i = 0; i < chunkSize; i++)
 0328                {
 0329                    byte ks = DecryptByte(_key2);
 0330                    byte p = buffer[i];
 0331                    workBuffer[i] = (byte)(p ^ ks);
 0332                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 0333                }
 334
 0335                _base.Write(workBuffer, 0, chunkSize);
 0336                buffer = buffer[chunkSize..];
 0337            }
 0338        }
 339
 340        protected override void Dispose(bool disposing)
 0341        {
 0342            if (_disposed)
 0343            {
 0344                return;
 345            }
 0346            _disposed = true;
 347
 0348            if (disposing)
 0349            {
 350                // If encrypted empty entry (no payload written), still must emit 12-byte header:
 0351                if (_encrypting)
 0352                {
 0353                    EnsureHeader();
 0354                }
 355
 0356                if (!_leaveOpen)
 0357                {
 0358                    _base.Dispose();
 0359                }
 0360            }
 0361            base.Dispose(disposing);
 0362        }
 363
 364        public override async ValueTask DisposeAsync()
 0365        {
 0366            if (_disposed)
 0367            {
 0368                return;
 369            }
 0370            _disposed = true;
 371
 372            // If encrypted empty entry (no payload written), still must emit 12-byte header:
 0373            if (_encrypting)
 0374            {
 0375                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
 0376            }
 0377            if (!_leaveOpen)
 0378            {
 0379                await _base.DisposeAsync().ConfigureAwait(false);
 0380            }
 381
 0382            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.
 0386        }
 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
 0395        {
 0396            ObjectDisposedException.ThrowIf(_disposed, this);
 0397            if (_encrypting)
 0398            {
 0399                throw new NotSupportedException(SR.ReadingNotSupported);
 400            }
 401
 0402            cancellationToken.ThrowIfCancellationRequested();
 0403            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 0404            Span<byte> span = buffer.Span;
 405
 0406            for (int i = 0; i < n; i++)
 0407            {
 0408                span[i] = DecryptAndUpdateKeys(span[i]);
 0409            }
 410
 0411            return n;
 0412        }
 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
 0421        {
 0422            ObjectDisposedException.ThrowIf(_disposed, this);
 0423            if (!_encrypting)
 0424            {
 0425                throw new NotSupportedException(SR.WritingNotSupported);
 426            }
 427
 0428            cancellationToken.ThrowIfCancellationRequested();
 429
 0430            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
 431
 0432            byte[] workBuffer = GetWriteWorkBuffer();
 433
 0434            while (!buffer.IsEmpty)
 0435            {
 0436                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 0437                ReadOnlySpan<byte> chunk = buffer.Span[..chunkSize];
 438
 0439                for (int i = 0; i < chunkSize; i++)
 0440                {
 0441                    byte ks = DecryptByte(_key2);
 0442                    byte p = chunk[i];
 0443                    workBuffer[i] = (byte)(p ^ ks);
 0444                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 0445                }
 446
 0447                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
 0448                buffer = buffer[chunkSize..];
 0449            }
 0450        }
 451
 452        public override Task FlushAsync(CancellationToken cancellationToken)
 0453        {
 0454            ObjectDisposedException.ThrowIf(_disposed, this);
 0455            return _base.FlushAsync(cancellationToken);
 0456        }
 457
 0458        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) =>
 015            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>)