Clean up usage of ArrayPool in the Cryptography assemblies
authorJeremy Barton <jbarton@microsoft.com>
Fri, 17 May 2019 13:28:17 +0000 (06:28 -0700)
committerGitHub <noreply@github.com>
Fri, 17 May 2019 13:28:17 +0000 (06:28 -0700)
The primary motivator of this change is to prepare for moving cryptography from
the public, shared ArrayPool instance to a private pool (or pools), just as a defense
in depth strategy.  Since only the shared ArrayPool instance has GC cooperation
the change to a private pool is not happening at this time.

What this change does provide:
* Every ArrayPool.Rent was identified as being private to the assembly or shared
  out.  If the array gets shared out, it is not appropriate to use a private pool.
* CryptoPool clears returned arrays by default. Instead of an optional bool for
  clearing it has an optional length to clear (from index 0).
* Some rents were removed altogether, via a couple strategies:
  * Opportunistic stackalloc
  * Add a SpanAction-based AsnWriter.WriteBitString overload to avoid the
    pattern of rent-write-call-copy (instead do request-write-done).
  * Add AsnWriter.ValueEquals to prevent rent-encode-compare.
* At least one Rent with no Return was found and fixed.
* Fixed several Rent-growth strategies that could double-Return on exceptions.
* Changed Rfc2898DeriveBytes to just use the one field array instead of a
   lot of rent-write-copy-to-the-field.
* Moves some Return calls out of finally blocks in Async methods to avoid a
  Return-while-in-use path with Task composition when one Task gets aborted
  and another is still running.

Commit migrated from https://github.com/dotnet/corefx/commit/64609475281f2be05a0b9ca5b7bbdaf43b12dc55

58 files changed:
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.PooledCrypto.cs [new file with mode: 0644]
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.BitString.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.GeneralizedTime.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Integer.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.OctetString.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Oid.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Text.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.UtcTime.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.BitString.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Oid.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Text.cs
src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.cs
src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs
src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs [new file with mode: 0644]
src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs
src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs
src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs
src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs
src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs
src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs
src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs
src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs
src/libraries/Common/src/System/Security/Cryptography/RSACng.EncryptDecrypt.cs
src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs
src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs
src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/OpenSslCipher.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2898Tests.cs
src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj
src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj
src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj
src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj
src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Asn.cs
src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs
src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs
src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/HelpersWindows.cs
src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj
src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs
src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs
src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs
src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs
src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs8PrivateKeyInfo.cs
src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs
src/libraries/System.Security.Cryptography.Primitives/src/System.Security.Cryptography.Primitives.csproj
src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/AsymmetricAlgorithm.cs
src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs
src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/HashAlgorithm.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj

index 8671e2e..6190ec0 100644 (file)
@@ -3,10 +3,8 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Buffers;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
-using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
 using Microsoft.Win32.SafeHandles;
 
@@ -131,11 +129,6 @@ internal static partial class Interop
             return GetDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1);
         }
 
-        internal static ArraySegment<byte> RentAsn1StringBytes(IntPtr asn1)
-        {
-            return RentDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1);
-        }
-
         internal static byte[] GetX509Thumbprint(SafeX509Handle x509)
         {
             return GetDynamicBuffer((handle, buf, i) => GetX509Thumbprint(handle, buf, i), x509);
@@ -218,28 +211,5 @@ internal static partial class Interop
 
             return bytes;
         }
-
-        private static ArraySegment<byte> RentDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle> method, THandle handle)
-        {
-            int negativeSize = method(handle, null, 0);
-
-            if (negativeSize > 0)
-            {
-                throw Interop.Crypto.CreateOpenSslCryptographicException();
-            }
-
-            int targetSize = -negativeSize;
-            byte[] bytes = ArrayPool<byte>.Shared.Rent(targetSize);
-
-            int ret = method(handle, bytes, targetSize);
-
-            if (ret != 1)
-            {
-                ArrayPool<byte>.Shared.Return(bytes);
-                throw Interop.Crypto.CreateOpenSslCryptographicException();
-            }
-
-            return new ArraySegment<byte>(bytes, 0, targetSize);
-        }
     }
 }
index 309ab2a..433bbfa 100644 (file)
@@ -3,9 +3,9 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Buffers;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
+using System.Security.Cryptography;
 
 internal static partial class Interop
 {
@@ -59,7 +59,7 @@ internal static partial class Interop
                 throw CreateOpenSslCryptographicException();
             }
 
-            byte[] data = ArrayPool<byte>.Shared.Rent(size);
+            byte[] data = CryptoPool.Rent(size);
 
             int size2 = encode(handle, data);
             if (size2 < 1)
@@ -67,9 +67,10 @@ internal static partial class Interop
                 Debug.Fail(
                     $"{nameof(OpenSslEncode)}: {nameof(getSize)} succeeded ({size}) and {nameof(encode)} failed ({size2})");
 
-                // Since we don't know what was written, assume it was secret and clear the value.
+                // Since we don't know what was written, assume it was secret and have the
+                // CryptoPool.Return clear the whole array.
                 // (It doesn't matter much, since we're behind Debug.Fail)
-                ArrayPool<byte>.Shared.Return(data, clearArray: true);
+                CryptoPool.Return(data);
 
                 // If it ever happens, ensure the error queue gets cleared.
                 // And since it didn't write the data, reporting an exception is good too.
diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.PooledCrypto.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.PooledCrypto.cs
new file mode 100644 (file)
index 0000000..ddcba2d
--- /dev/null
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Security.Cryptography;
+
+internal static partial class Interop
+{
+    internal static partial class Crypto
+    {
+        internal static ArraySegment<byte> RentAsn1StringBytes(IntPtr asn1)
+        {
+            return RentDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1);
+        }
+
+        private static ArraySegment<byte> RentDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle> method, THandle handle)
+        {
+            int negativeSize = method(handle, null, 0);
+
+            if (negativeSize > 0)
+            {
+                throw Interop.Crypto.CreateOpenSslCryptographicException();
+            }
+
+            int targetSize = -negativeSize;
+            byte[] bytes = CryptoPool.Rent(targetSize);
+
+            int ret = method(handle, bytes, targetSize);
+
+            if (ret != 1)
+            {
+                CryptoPool.Return(bytes);
+                throw Interop.Crypto.CreateOpenSslCryptographicException();
+            }
+
+            return new ArraySegment<byte>(bytes, 0, targetSize);
+        }
+    }
+}
index fb12032..2d03931 100644 (file)
@@ -351,7 +351,7 @@ namespace System.Security.Cryptography.Asn1
             memory = PeekEncodedValue();
 
             // Guaranteed long enough
-            byte[] rented = ArrayPool<byte>.Shared.Rent(memory.Length);
+            byte[] rented = CryptoPool.Rent(memory.Length);
             int dataLength = 0;
 
             try
@@ -368,8 +368,7 @@ namespace System.Security.Cryptography.Asn1
             }
             finally
             {
-                rented.AsSpan(0, dataLength).Clear();
-                ArrayPool<byte>.Shared.Return(rented);
+                CryptoPool.Return(rented, dataLength);
             }
         }
 
index 59b0eae..e1ad8c1 100644 (file)
@@ -56,18 +56,22 @@ namespace System.Security.Cryptography.Asn1
         {
             byte[] rented = null;
 
+            // An X.509 time is 15 characters (yyyyMMddHHmmssZ), beyond that is fractions (no limit) or
+            // BER specified offset.
+            Span<byte> tmpSpace = stackalloc byte[64];
+
             ReadOnlySpan<byte> contents = GetOctetStringContents(
                 expectedTag,
                 UniversalTagNumber.GeneralizedTime,
                 out int bytesRead,
-                ref rented);
+                ref rented,
+                tmpSpace);
 
             DateTimeOffset value = ParseGeneralizedTime(RuleSet, contents, disallowFractions);
 
             if (rented != null)
             {
-                Array.Clear(rented, 0, contents.Length);
-                ArrayPool<byte>.Shared.Return(rented);
+                CryptoPool.Return(rented, contents.Length);
             }
 
             _data = _data.Slice(bytesRead);
index 22b3a6c..96275d0 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers;
 using System.Diagnostics;
 using System.Numerics;
 
@@ -92,7 +91,7 @@ namespace System.Security.Cryptography.Asn1
                 GetIntegerContents(expectedTag, UniversalTagNumber.Integer, out int headerLength);
 
             // TODO: Split this for netcoreapp/netstandard to use the Big-Endian BigInteger parsing
-            byte[] tmp = ArrayPool<byte>.Shared.Rent(contents.Length);
+            byte[] tmp = CryptoPool.Rent(contents.Length);
             BigInteger value;
 
             try
@@ -107,9 +106,9 @@ namespace System.Security.Cryptography.Asn1
             }
             finally
             {
-                // Clear the whole tmp so that not even the sign bit is returned to the array pool.
-                Array.Clear(tmp, 0, tmp.Length);
-                ArrayPool<byte>.Shared.Return(tmp);
+                // Let CryptoPool.Return clear the whole tmp so that not even the sign bit
+                // is returned to the array pool.
+                CryptoPool.Return(tmp);
             }
 
             _data = _data.Slice(headerLength + contents.Length);
index fb2a983..5394dbe 100644 (file)
@@ -230,7 +230,7 @@ namespace System.Security.Cryptography.Asn1
             memory = PeekEncodedValue();
 
             // Guaranteed long enough
-            byte[] rented = ArrayPool<byte>.Shared.Rent(memory.Length);
+            byte[] rented = CryptoPool.Rent(memory.Length);
             int dataLength = 0;
 
             try
@@ -247,8 +247,7 @@ namespace System.Security.Cryptography.Asn1
             }
             finally
             {
-                rented.AsSpan(0, dataLength).Clear();
-                ArrayPool<byte>.Shared.Return(rented);
+                CryptoPool.Return(rented, dataLength);
             }
         }
 
@@ -571,7 +570,7 @@ namespace System.Security.Cryptography.Asn1
 
             if (tmpSpace.Length < octetStringLength)
             {
-                rented = ArrayPool<byte>.Shared.Rent(octetStringLength);
+                rented = CryptoPool.Rent(octetStringLength);
                 tmpSpace = rented;
             }
 
index 8a5f210..e767cf5 100644 (file)
@@ -152,7 +152,7 @@ namespace System.Security.Cryptography.Asn1
             // Every 8 content bytes turns into 7 integer bytes, so scale the count appropriately.
             // Add one while we're shrunk to account for the needed padding byte or the len%8 discarded bytes.
             int bytesRequired = ((bytesRead / ContentByteCount) + 1) * SemanticByteCount;
-            byte[] tmpBytes = ArrayPool<byte>.Shared.Rent(bytesRequired);
+            byte[] tmpBytes = CryptoPool.Rent(bytesRequired);
             // Ensure all the bytes are zeroed out for BigInteger's parsing.
             Array.Clear(tmpBytes, 0, tmpBytes.Length);
 
@@ -202,8 +202,7 @@ namespace System.Security.Cryptography.Asn1
             largeValue = new BigInteger(tmpBytes);
             smallValue = null;
 
-            Array.Clear(tmpBytes, 0, bytesWritten);
-            ArrayPool<byte>.Shared.Return(tmpBytes);
+            CryptoPool.Return(tmpBytes, bytesWritten);
         }
 
         private string ReadObjectIdentifierAsString(Asn1Tag expectedTag, out int totalBytesRead)
index b51b799..5c1dd29 100644 (file)
@@ -656,8 +656,7 @@ namespace System.Security.Cryptography.Asn1
             {
                 if (rented != null)
                 {
-                    Array.Clear(rented, 0, contents.Length);
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptoPool.Return(rented, contents.Length);
                 }
             }
         }
@@ -697,8 +696,7 @@ namespace System.Security.Cryptography.Asn1
             {
                 if (rented != null)
                 {
-                    Array.Clear(rented, 0, contents.Length);
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptoPool.Return(rented, contents.Length);
                 }
             }
         }
index 24ff44a..24c0b14 100644 (file)
@@ -86,8 +86,7 @@ namespace System.Security.Cryptography.Asn1
             if (rented != null)
             {
                 Debug.Fail($"UtcTime did not fit in tmpSpace ({contents.Length} total)");
-                Array.Clear(rented, 0, contents.Length);
-                ArrayPool<byte>.Shared.Return(rented);
+                CryptoPool.Return(rented, contents.Length);
             }
 
             _data = _data.Slice(bytesRead);
index 89ae861..a5a0675 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Buffers;
 using System.Diagnostics;
 
 namespace System.Security.Cryptography.Asn1
@@ -80,19 +81,15 @@ namespace System.Security.Cryptography.Asn1
                 throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
             }
 
-            // If 3 bits are "unused" then build a mask for them to check for 0.
-            // 1 << 3 => 0b0000_1000
-            // subtract 1 => 0b000_0111
-            int mask = (1 << unusedBitCount) - 1;
             byte lastByte = bitString.IsEmpty ? (byte)0 : bitString[bitString.Length - 1];
 
-            if ((lastByte & mask) != 0)
+            // T-REC-X.690-201508 sec 11.2
+            //
+            // This could be ignored for BER, but since DER is more common and
+            // it likely suggests a program error on the caller, leave it enabled for
+            // BER for now.
+            if (!CheckValidLastByte(lastByte, unusedBitCount))
             {
-                // T-REC-X.690-201508 sec 11.2
-                //
-                // This could be ignored for BER, but since DER is more common and
-                // it likely suggests a program error on the caller, leave it enabled for
-                // BER for now.
                 // TODO: Probably warrants a distinct message.
                 throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
             }
@@ -120,20 +117,190 @@ namespace System.Security.Cryptography.Asn1
             _offset += bitString.Length;
         }
 
-        // T-REC-X.690-201508 sec 9.2, 8.6
-        private void WriteConstructedCerBitString(Asn1Tag tag, ReadOnlySpan<byte> payload, int unusedBitCount)
+#if netcoreapp || uap || NETCOREAPP
+        /// <summary>
+        ///   Write a Bit String value via a callback, with a tag UNIVERSAL 3.
+        /// </summary>
+        /// <param name="byteLength">The total number of bytes to write.</param>
+        /// <param name="state">A state object to pass to <paramref name="action"/>.</param>
+        /// <param name="action">A callback to invoke for populating the Bit String.</param>
+        /// <param name="unusedBitCount">
+        ///   The number of trailing bits which are not semantic.
+        /// </param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="byteLength"/> is negative --OR--
+        ///   <paramref name="unusedBitCount"/> is not in the range [0,7]
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="byteLength"/> is 0 and <paramref name="unusedBitCount"/> is not 0 --OR--
+        ///   <paramref name="byteLength"/> is not 0 and any of the bits identified by
+        ///   <paramref name="unusedBitCount"/> is set
+        /// </exception>
+        /// <exception cref="ObjectDisposedException">The writer has been Disposed.</exception>
+        public void WriteBitString<TState>(
+            int byteLength,
+            TState state,
+            SpanAction<byte, TState> action,
+            int unusedBitCount = 0)
+        {
+            WriteBitStringCore(Asn1Tag.PrimitiveBitString, byteLength, state, action, unusedBitCount);
+        }
+
+        /// <summary>
+        ///   Write a Bit String value via a callback, with a specified tag.
+        /// </summary>
+        /// <param name="tag">The tag to write.</param>
+        /// <param name="byteLength">The total number of bytes to write.</param>
+        /// <param name="state">A state object to pass to <paramref name="action"/>.</param>
+        /// <param name="action">A callback to invoke for populating the Bit String.</param>
+        /// <param name="unusedBitCount">
+        ///   The number of trailing bits which are not semantic.
+        /// </param>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
+        ///   <see cref="TagClass.Universal"/>, but
+        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
+        ///   the method
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="byteLength"/> is negative --OR--
+        ///   <paramref name="unusedBitCount"/> is not in the range [0,7]
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="byteLength"/> is 0 and <paramref name="unusedBitCount"/> is not 0 --OR--
+        ///   <paramref name="byteLength"/> is not 0 and any of the bits identified by
+        ///   <paramref name="unusedBitCount"/> is set
+        /// </exception>
+        /// <exception cref="ObjectDisposedException">The writer has been Disposed.</exception>
+        public void WriteBitString<TState>(
+            Asn1Tag tag,
+            int byteLength,
+            TState state,
+            SpanAction<byte, TState> action,
+            int unusedBitCount = 0)
+        {
+            CheckUniversalTag(tag, UniversalTagNumber.BitString);
+
+            // Primitive or constructed, doesn't matter.
+            WriteBitStringCore(tag, byteLength, state, action, unusedBitCount);
+        }
+
+        // T-REC-X.690-201508 sec 8.6
+        private void WriteBitStringCore<TState>(
+            Asn1Tag tag,
+            int byteLength,
+            TState state,
+            SpanAction<byte, TState> action,
+            int unusedBitCount = 0)
+        {
+            if (byteLength == 0)
+            {
+                WriteBitStringCore(tag, ReadOnlySpan<byte>.Empty, unusedBitCount);
+                return;
+            }
+
+            // T-REC-X.690-201508 sec 8.6.2.2
+            if (unusedBitCount < 0 || unusedBitCount > 7)
+            {
+                throw new ArgumentOutOfRangeException(
+                    nameof(unusedBitCount),
+                    unusedBitCount,
+                    SR.Cryptography_Asn_UnusedBitCountRange);
+            }
+
+            CheckDisposed();
+
+            int savedOffset = _offset;
+            Span<byte> scratchSpace;
+            byte[] ensureNoExtraCopy = null;
+            int expectedSize = 0;
+
+            // T-REC-X.690-201508 sec 9.2
+            //
+            // If it's not within a primitive segment, use the constructed encoding.
+            // (>= instead of > because of the unused bit count byte)
+            bool segmentedWrite =
+                RuleSet == AsnEncodingRules.CER && byteLength >= AsnReader.MaxCERSegmentSize;
+
+            if (segmentedWrite)
+            {
+                // Rather than call the callback multiple times, grow the buffer to allow
+                // for enough space for the final output, then return a buffer where the last segment
+                // is in the correct place. (Data will shift backwards to the right spot while writing
+                // other segments).
+                expectedSize = DetermineCerBitStringTotalLength(tag, byteLength);
+                EnsureWriteCapacity(expectedSize);
+                int overhead = expectedSize - byteLength;
+
+                // Start writing where the last content byte is in the correct place, which is
+                // after all of the overhead, but ending before the two byte end-of-contents marker.
+                int scratchStart = overhead - 2;
+                ensureNoExtraCopy = _buffer;
+                scratchSpace = _buffer.AsSpan(scratchStart, byteLength);
+
+                // Don't let gapped-writes be unpredictable.
+                scratchSpace.Clear();
+            }
+            else
+            {
+                WriteTag(tag.AsPrimitive());
+                // The unused bits byte requires +1.
+                WriteLength(byteLength + 1);
+
+                _buffer[_offset] = (byte)unusedBitCount;
+                _offset++;
+
+                scratchSpace = _buffer.AsSpan(_offset, byteLength);
+            }
+
+            action(scratchSpace, state);
+
+            // T-REC-X.690-201508 sec 11.2
+            //
+            // This could be ignored for BER, but since DER is more common and
+            // it likely suggests a program error on the caller, leave it enabled for
+            // BER for now.
+            if (!CheckValidLastByte(scratchSpace[byteLength - 1], unusedBitCount))
+            {
+                // Since we are restoring _offset we won't clear this on a grow or Dispose,
+                // so clear it now.
+                _offset = savedOffset;
+                scratchSpace.Clear();
+
+                // TODO: Probably warrants a distinct message.
+                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+            }
+
+            if (segmentedWrite)
+            {
+                WriteConstructedCerBitString(tag, scratchSpace, unusedBitCount);
+                Debug.Assert(_offset - savedOffset == expectedSize, $"expected size was {expectedSize}, actual was {_offset - savedOffset}");
+                Debug.Assert(_buffer == ensureNoExtraCopy, $"_buffer was replaced during while writing a bit string via callback");
+            }
+            else
+            {
+                _offset += byteLength;
+            }
+        }
+#endif
+
+        private static bool CheckValidLastByte(byte lastByte, int unusedBitCount)
+        {
+            // If 3 bits are "unused" then build a mask for them to check for 0.
+            // 1 << 3 => 0b0000_1000
+            // subtract 1 => 0b000_0111
+            int mask = (1 << unusedBitCount) - 1;
+            return ((lastByte & mask) == 0);
+        }
+
+        private static int DetermineCerBitStringTotalLength(Asn1Tag tag, int contentLength)
         {
             const int MaxCERSegmentSize = AsnReader.MaxCERSegmentSize;
             // Every segment has an "unused bit count" byte.
             const int MaxCERContentSize = MaxCERSegmentSize - 1;
-            Debug.Assert(payload.Length > MaxCERContentSize);
+            Debug.Assert(contentLength > MaxCERContentSize);
 
-            WriteTag(tag.AsConstructed());
-            // T-REC-X.690-201508 sec 9.1
-            // Constructed CER uses the indefinite form.
-            WriteLength(-1);
-
-            int fullSegments = Math.DivRem(payload.Length, MaxCERContentSize, out int lastContentSize);
+            int fullSegments = Math.DivRem(contentLength, MaxCERContentSize, out int lastContentSize);
 
             // The tag size is 1 byte.
             // The length will always be encoded as 82 03 E8 (3 bytes)
@@ -156,11 +323,29 @@ namespace System.Security.Cryptography.Asn1
 
             // Reduce the number of copies by pre-calculating the size.
             // +2 for End-Of-Contents
-            int expectedSize = fullSegments * FullSegmentEncodedSize + remainingEncodedSize + 2;
+            // +1 for 0x80 indefinite length
+            // +tag length
+            return fullSegments * FullSegmentEncodedSize + remainingEncodedSize + 3 + tag.CalculateEncodedSize();
+        }
+
+        // T-REC-X.690-201508 sec 9.2, 8.6
+        private void WriteConstructedCerBitString(Asn1Tag tag, ReadOnlySpan<byte> payload, int unusedBitCount)
+        {
+            const int MaxCERSegmentSize = AsnReader.MaxCERSegmentSize;
+            // Every segment has an "unused bit count" byte.
+            const int MaxCERContentSize = MaxCERSegmentSize - 1;
+            Debug.Assert(payload.Length > MaxCERContentSize);
+
+            int expectedSize = DetermineCerBitStringTotalLength(tag, payload.Length);
             EnsureWriteCapacity(expectedSize);
+            int savedOffset = _offset;
+
+            WriteTag(tag.AsConstructed());
+            // T-REC-X.690-201508 sec 9.1
+            // Constructed CER uses the indefinite form.
+            WriteLength(-1);
 
             byte[] ensureNoExtraCopy = _buffer;
-            int savedOffset = _offset;
 
             ReadOnlySpan<byte> remainingData = payload;
             Span<byte> dest;
index 47c8d52..f8c64e1 100644 (file)
@@ -169,8 +169,7 @@ namespace System.Security.Cryptography.Asn1
             // The worst case is "1.1.1.1.1", which takes 4 bytes (5 components, with the first two condensed)
             // Longer numbers get smaller: "2.1.127" is only 2 bytes. (81d (0x51) and 127 (0x7F))
             // So length / 2 should prevent any reallocations.
-            var localPool = ArrayPool<byte>.Shared;
-            byte[] tmp = localPool.Rent(oidValue.Length / 2);
+            byte[] tmp = CryptoPool.Rent(oidValue.Length / 2);
             int tmpOffset = 0;
 
             try
@@ -227,8 +226,7 @@ namespace System.Security.Cryptography.Asn1
             }
             finally
             {
-                Array.Clear(tmp, 0, tmpOffset);
-                localPool.Return(tmp);
+                CryptoPool.Return(tmp, tmpOffset);
             }
         }
 
index 348c24c..83cb6e8 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 
@@ -177,13 +176,11 @@ namespace System.Security.Cryptography.Asn1
 
             byte[] tmp;
 
-            // TODO: Split this for netstandard vs netcoreapp for span?.
-            var localPool = ArrayPool<byte>.Shared;
             unsafe
             {
                 fixed (char* strPtr = &MemoryMarshal.GetReference(str))
                 {
-                    tmp = localPool.Rent(size);
+                    tmp = CryptoPool.Rent(size);
 
                     fixed (byte* destPtr = tmp)
                     {
@@ -200,8 +197,7 @@ namespace System.Security.Cryptography.Asn1
             }
 
             WriteConstructedCerOctetString(tag, tmp.AsSpan(0, size));
-            Array.Clear(tmp, 0, size);
-            localPool.Return(tmp);
+            CryptoPool.Return(tmp, size);
         }
     }
 }
index 2b4c282..ad0ce19 100644 (file)
@@ -57,7 +57,8 @@ namespace System.Security.Cryptography.Asn1
             {
                 Array.Clear(_buffer, 0, _offset);
 #if !CHECK_ACCURATE_ENSURE
-                ArrayPool<byte>.Shared.Return(_buffer);
+                // clearSize: 0 because it was already cleared.
+                CryptoPool.Return(_buffer, clearSize: 0);
 #endif
                 _buffer = null;
             }
@@ -192,6 +193,24 @@ namespace System.Security.Cryptography.Asn1
             return new ReadOnlySpan<byte>(_buffer, 0, _offset);
         }
 
+        /// <summary>
+        ///   Determines if <see cref="Encode"/> would produce an output identical to
+        ///   <paramref name="other"/>.
+        /// </summary>
+        /// <returns>
+        ///   <see langword="true"/> if the pending encoded data is identical to <paramref name="other"/>,
+        ///   <see langword="false"/> otherwise.
+        /// </returns>
+        /// <exception cref="InvalidOperationException">
+        ///   A <see cref="PushSequence()"/> or <see cref="PushSetOf()"/> has not been closed via
+        ///   <see cref="PopSequence()"/> or <see cref="PopSetOf()"/>.
+        /// </exception>
+        /// <exception cref="ObjectDisposedException">The writer has been Disposed.</exception>
+        public bool ValueEquals(ReadOnlySpan<byte> other)
+        {
+            return EncodeAsSpan().SequenceEqual(other);
+        }
+
         private void CheckDisposed()
         {
             if (_offset < 0)
@@ -226,26 +245,23 @@ namespace System.Security.Cryptography.Asn1
                 // While the ArrayPool may have similar logic, make sure we don't run into a lot of
                 // "grow a little" by asking in 1k steps.
                 int blocks = checked(_offset + pendingCount + (BlockSize - 1)) / BlockSize;
-                var localPool = ArrayPool<byte>.Shared;
-                byte[] newBytes = localPool.Rent(BlockSize * blocks);
+                byte[] oldBytes = _buffer;
+                _buffer = CryptoPool.Rent(BlockSize * blocks);
 
-                if (_buffer != null)
+                if (oldBytes != null)
                 {
-                    Buffer.BlockCopy(_buffer, 0, newBytes, 0, _offset);
-                    Array.Clear(_buffer, 0, _offset);
-                    localPool.Return(_buffer);
+                    Buffer.BlockCopy(oldBytes, 0, _buffer, 0, _offset);
+                    CryptoPool.Return(oldBytes, _offset);
                 }
 #endif
 
 #if DEBUG
                 // Ensure no "implicit 0" is happening
-                for (int i = _offset; i < newBytes.Length; i++)
+                for (int i = _offset; i < _buffer.Length; i++)
                 {
-                    newBytes[i] ^= 0xFF;
+                    _buffer[i] ^= 0xFF;
                 }
 #endif
-
-                _buffer = newBytes;
             }
         }
 
@@ -506,8 +522,7 @@ namespace System.Security.Cryptography.Asn1
             var comparer = new ArrayIndexSetOfValueComparer(buffer);
             positions.Sort(comparer);
 
-            ArrayPool<byte> localPool = ArrayPool<byte>.Shared;
-            byte[] tmp = localPool.Rent(len);
+            byte[] tmp = CryptoPool.Rent(len);
 
             pos = 0;
 
@@ -520,8 +535,7 @@ namespace System.Security.Cryptography.Asn1
             Debug.Assert(pos == len);
 
             Buffer.BlockCopy(tmp, 0, buffer, start, len);
-            Array.Clear(tmp, 0, len);
-            localPool.Return(tmp);
+            CryptoPool.Return(tmp, len);
         }
 
         internal static void Reverse(Span<byte> span)
index 652de7d..6316db7 100644 (file)
@@ -151,7 +151,7 @@ namespace System.Security.Cryptography
                     finally
                     {
                         CryptographicOperations.ZeroMemory(decryptedSpan);
-                        ArrayPool<byte>.Shared.Return(decrypted.Array);
+                        CryptoPool.Return(decrypted.Array);
                     }
                 }
             }
@@ -203,7 +203,7 @@ namespace System.Security.Cryptography
                     finally
                     {
                         CryptographicOperations.ZeroMemory(decryptedSpan);
-                        ArrayPool<byte>.Shared.Return(decrypted.Array);
+                        CryptoPool.Return(decrypted.Array, clearSize: 0);
                     }
                 }
             }
@@ -233,7 +233,7 @@ namespace System.Security.Cryptography
             //  * secp521r1 needs ~300 bytes (named) or ~730 (explicit)
             //  * KeySize (bits) should avoid re-rent for named, and probably
             //    gets one re-rent for explicit.
-            byte[] rented = ArrayPool<byte>.Shared.Rent(key.KeySize);
+            byte[] rented = CryptoPool.Rent(key.KeySize);
             int rentWritten = 0;
 
             // If we use 6 bits from each byte, that's 22 * 6 = 132
@@ -250,8 +250,9 @@ namespace System.Security.Cryptography
                     out rentWritten))
                 {
                     int size = rented.Length;
-                    ArrayPool<byte>.Shared.Return(rented);
-                    rented = ArrayPool<byte>.Shared.Rent(checked(size * 2));
+                    byte[] current = rented;
+                    rented = CryptoPool.Rent(checked(size * 2));
+                    CryptoPool.Return(current, rentWritten);
                 }
 
                 return KeyFormatHelper.ReencryptPkcs8(
@@ -263,8 +264,7 @@ namespace System.Security.Cryptography
             finally
             {
                 randomString.Clear();
-                CryptographicOperations.ZeroMemory(rented.AsSpan(0, rentWritten));
-                ArrayPool<byte>.Shared.Return(rented);
+                CryptoPool.Return(rented, rentWritten);
             }
         }
 
@@ -275,7 +275,7 @@ namespace System.Security.Cryptography
         {
             Debug.Assert(pbeParameters != null);
 
-            byte[] rented = ArrayPool<byte>.Shared.Rent(key.KeySize);
+            byte[] rented = CryptoPool.Rent(key.KeySize);
             int rentWritten = 0;
 
             try
@@ -287,8 +287,9 @@ namespace System.Security.Cryptography
                     out rentWritten))
                 {
                     int size = rented.Length;
-                    ArrayPool<byte>.Shared.Return(rented);
-                    rented = ArrayPool<byte>.Shared.Rent(checked(size * 2));
+                    byte[] current = rented;
+                    rented = CryptoPool.Rent(checked(size * 2));
+                    CryptoPool.Return(current, rentWritten);
                 }
 
                 return KeyFormatHelper.ReencryptPkcs8(
@@ -299,8 +300,7 @@ namespace System.Security.Cryptography
             }
             finally
             {
-                CryptographicOperations.ZeroMemory(rented.AsSpan(0, rentWritten));
-                ArrayPool<byte>.Shared.Return(rented);
+                CryptoPool.Return(rented, rentWritten);
             }
         }
 
diff --git a/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs b/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs
new file mode 100644 (file)
index 0000000..9d6413e
--- /dev/null
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+
+namespace System.Security.Cryptography
+{
+    internal static class CryptoPool
+    {
+        internal const int ClearAll = -1;
+
+        internal static byte[] Rent(int minimumLength) => ArrayPool<byte>.Shared.Rent(minimumLength);
+
+        internal static void Return(byte[] array, int clearSize = ClearAll)
+        {
+            Debug.Assert(clearSize <= array.Length);
+            bool clearWholeArray = clearSize < 0;
+
+            if (!clearWholeArray && clearSize != 0)
+            {
+#if netcoreapp || uap || NETCOREAPP
+                CryptographicOperations.ZeroMemory(array.AsSpan(0, clearSize));
+#else
+                Array.Clear(array, 0, clearSize);
+#endif
+            }
+
+            ArrayPool<byte>.Shared.Return(array, clearWholeArray);
+        }
+    }
+}
index 2b2452d..9fb82a9 100644 (file)
@@ -2,7 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers;
+using System.Diagnostics;
 using Internal.Cryptography;
 using Microsoft.Win32.SafeHandles;
 using static Interop.BCrypt;
@@ -16,6 +16,12 @@ namespace System.Security.Cryptography
 #endif
         public sealed partial class DSACng : DSA
         {
+            // As of FIPS 186-4 the maximum Q size is 32 bytes.
+            //
+            // See also: cbGroupSize at
+            // https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/ns-bcrypt-_bcrypt_dsa_key_blob_v2
+            private const int WindowsMaxQSize = 32;
+
             public override byte[] CreateSignature(byte[] rgbHash)
             {
                 if (rgbHash == null)
@@ -23,45 +29,26 @@ namespace System.Security.Cryptography
                     throw new ArgumentNullException(nameof(rgbHash));
                 }
 
-                ReadOnlySpan<byte> source = rgbHash;
-                byte[] arrayToReturnToArrayPool = AdjustHashSizeIfNecessaryWithArrayPool(ref source);
-                try
-                {
-                    using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
-                    {
-                        unsafe
-                        {
-                            return CngCommon.SignHash(keyHandle, source, AsymmetricPaddingMode.None, null, source.Length * 2);
-                        }
-                    }
-                }
-                finally
+                Span<byte> stackBuf = stackalloc byte[WindowsMaxQSize];
+                ReadOnlySpan<byte> source = AdjustHashSizeIfNecessary(rgbHash, stackBuf);
+
+                using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
                 {
-                    if (arrayToReturnToArrayPool != null)
+                    unsafe
                     {
-                        Array.Clear(arrayToReturnToArrayPool, 0, source.Length);
-                        ArrayPool<byte>.Shared.Return(arrayToReturnToArrayPool);
+                        return CngCommon.SignHash(keyHandle, source, AsymmetricPaddingMode.None, null, source.Length * 2);
                     }
                 }
             }
 
             public override unsafe bool TryCreateSignature(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
             {
-                byte[] arrayToReturnToArrayPool = AdjustHashSizeIfNecessaryWithArrayPool(ref hash);
-                try
+                Span<byte> stackBuf = stackalloc byte[WindowsMaxQSize];
+                ReadOnlySpan<byte> source = AdjustHashSizeIfNecessary(hash, stackBuf);
+
+                using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
                 {
-                    using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
-                    {
-                        return CngCommon.TrySignHash(keyHandle, hash, destination, AsymmetricPaddingMode.None, null, out bytesWritten);
-                    }
-                }
-                finally
-                {
-                    if (arrayToReturnToArrayPool != null)
-                    {
-                        Array.Clear(arrayToReturnToArrayPool, 0, hash.Length);
-                        ArrayPool<byte>.Shared.Return(arrayToReturnToArrayPool);
-                    }
+                    return CngCommon.TrySignHash(keyHandle, source, destination, AsymmetricPaddingMode.None, null, out bytesWritten);
                 }
             }
 
@@ -81,29 +68,22 @@ namespace System.Security.Cryptography
 
             public override bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature)
             {
-                byte[] arrayToReturnToArrayPool = AdjustHashSizeIfNecessaryWithArrayPool(ref hash);
-                try
-                {
-                    using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
-                    {
-                        unsafe
-                        {
-                            return CngCommon.VerifyHash(keyHandle, hash, signature, AsymmetricPaddingMode.None, null);
-                        }
-                    }
-                }
-                finally
+                Span<byte> stackBuf = stackalloc byte[WindowsMaxQSize];
+                ReadOnlySpan<byte> source = AdjustHashSizeIfNecessary(hash, stackBuf);
+
+                using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
                 {
-                    if (arrayToReturnToArrayPool != null)
+                    unsafe
                     {
-                        Array.Clear(arrayToReturnToArrayPool, 0, hash.Length);
-                        ArrayPool<byte>.Shared.Return(arrayToReturnToArrayPool);
+                        return CngCommon.VerifyHash(keyHandle, source, signature, AsymmetricPaddingMode.None, null);
                     }
                 }
             }
 
-            private byte[] AdjustHashSizeIfNecessaryWithArrayPool(ref ReadOnlySpan<byte> hash)
+            private ReadOnlySpan<byte> AdjustHashSizeIfNecessary(ReadOnlySpan<byte> hash, Span<byte> stackBuf)
             {
+                Debug.Assert(stackBuf.Length == WindowsMaxQSize);
+                
                 // Windows CNG requires that the hash output and q match sizes, but we can better
                 // interoperate with other FIPS 186-3 implementations if we perform truncation
                 // here, before sending it to CNG. Since this is a scenario presented in the
@@ -114,23 +94,22 @@ namespace System.Security.Cryptography
                 // presented in the CAVP reference test suite, we can confirm our implementation.
 
                 int qLength = ComputeQLength();
+                Debug.Assert(qLength <= WindowsMaxQSize);
 
                 if (qLength == hash.Length)
                 {
-                    return null;
+                    return hash;
                 }
-                else if (qLength < hash.Length)
-                {
-                    hash = hash.Slice(0, qLength);
-                    return null;
-                }
-                else
+
+                if (qLength < hash.Length)
                 {
-                    byte[] arrayPoolPaddedHash = ArrayPool<byte>.Shared.Rent(qLength);
-                    hash.CopyTo(new Span<byte>(arrayPoolPaddedHash, qLength - hash.Length, hash.Length));
-                    hash = new ReadOnlySpan<byte>(arrayPoolPaddedHash, 0, qLength);
-                    return arrayPoolPaddedHash;
+                    return hash.Slice(0, qLength);
                 }
+
+                int zeroByteCount = qLength - hash.Length;
+                stackBuf.Slice(0, zeroByteCount).Clear();
+                hash.CopyTo(stackBuf.Slice(zeroByteCount));
+                return stackBuf.Slice(0, qLength);
             }
 
             private int ComputeQLength()
index e075975..f4be1e7 100644 (file)
@@ -190,7 +190,7 @@ namespace System.Security.Cryptography
 
                 SafeDsaHandle key = _key.Value;
                 int signatureSize = Interop.Crypto.DsaEncodedSignatureSize(key);
-                byte[] signature = ArrayPool<byte>.Shared.Rent(signatureSize);
+                byte[] signature = CryptoPool.Rent(signatureSize);
                 try
                 {
                     bool success = Interop.Crypto.DsaSign(key, rgbHash, new Span<byte>(signature, 0, signatureSize), out signatureSize);
@@ -211,8 +211,7 @@ namespace System.Security.Cryptography
                 }
                 finally
                 {
-                    Array.Clear(signature, 0, signatureSize);
-                    ArrayPool<byte>.Shared.Return(signature);
+                    CryptoPool.Return(signature, signatureSize);
                 }
             }
 
@@ -221,7 +220,7 @@ namespace System.Security.Cryptography
                 byte[] converted;
                 SafeDsaHandle key = _key.Value;
                 int signatureSize = Interop.Crypto.DsaEncodedSignatureSize(key);
-                byte[] signature = ArrayPool<byte>.Shared.Rent(signatureSize);
+                byte[] signature = CryptoPool.Rent(signatureSize);
                 try
                 {
                     bool success = Interop.Crypto.DsaSign(key, hash, new Span<byte>(signature, 0, signatureSize), out signatureSize);
@@ -242,8 +241,7 @@ namespace System.Security.Cryptography
                 }
                 finally
                 {
-                    Array.Clear(signature, 0, signatureSize);
-                    ArrayPool<byte>.Shared.Return(signature);
+                    CryptoPool.Return(signature, signatureSize);
                 }
 
                 if (converted.Length <= destination.Length)
index 6426aef..8de7899 100644 (file)
@@ -157,7 +157,7 @@ namespace System.Security.Cryptography
 
                         if (secretLength > StackAllocMax)
                         {
-                            rented = ArrayPool<byte>.Shared.Rent(secretLength);
+                            rented = CryptoPool.Rent(secretLength);
                             secret = new Span<byte>(rented, 0, secretLength);
                         }
                         else
@@ -190,8 +190,7 @@ namespace System.Security.Cryptography
 
                     if (rented != null)
                     {
-                        Array.Clear(rented, 0, secretLength);
-                        ArrayPool<byte>.Shared.Return(rented);
+                        CryptoPool.Return(rented, secretLength);
                     }
                 }
             }
index 292fd65..4f5d488 100644 (file)
@@ -95,7 +95,7 @@ namespace System.Security.Cryptography
 
                 byte[] converted;
                 int signatureLength = Interop.Crypto.EcDsaSize(key);
-                byte[] signature = ArrayPool<byte>.Shared.Rent(signatureLength);
+                byte[] signature = CryptoPool.Rent(signatureLength);
                 try
                 {
                     if (!Interop.Crypto.EcDsaSign(hash, new Span<byte>(signature, 0, signatureLength), ref signatureLength, key))
@@ -107,8 +107,7 @@ namespace System.Security.Cryptography
                 }
                 finally
                 {
-                    Array.Clear(signature, 0, signatureLength);
-                    ArrayPool<byte>.Shared.Return(signature);
+                    CryptoPool.Return(signature, signatureLength);
                 }
 
                 if (converted.Length <= destination.Length)
index aca89ee..ca712fc 100644 (file)
@@ -213,29 +213,19 @@ namespace System.Security.Cryptography
             if (keyParameters != null && algId.Parameters != null)
             {
                 ReadOnlySpan<byte> algIdParameters = algId.Parameters.Value.Span;
-                byte[] verify = ArrayPool<byte>.Shared.Rent(algIdParameters.Length);
 
-                try
+                // X.509 SubjectPublicKeyInfo specifies DER encoding.
+                // RFC 5915 specifies DER encoding for EC Private Keys.
+                // So we can compare as DER.
+                using (AsnWriter writer = new AsnWriter(AsnEncodingRules.DER))
                 {
-                    // X.509 SubjectPublicKeyInfo specifies DER encoding.
-                    // RFC 5915 specifies DER encoding for EC Private Keys.
-                    // So we can compare as DER.
-                    using (AsnWriter writer = new AsnWriter(AsnEncodingRules.DER))
+                    keyParameters.Value.Encode(writer);
+
+                    if (!writer.ValueEquals(algIdParameters))
                     {
-                        keyParameters.Value.Encode(writer);
-                        if (!writer.TryEncode(verify, out int written) ||
-                            written != algIdParameters.Length ||
-                            !algIdParameters.SequenceEqual(new ReadOnlySpan<byte>(verify, 0, written)))
-                        {
-                            throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
-                        }
+                        throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                     }
                 }
-                finally
-                {
-                    // verify contains public information and does not need to be cleared.
-                    ArrayPool<byte>.Shared.Return(verify);
-                }
             }
         }
 
@@ -353,36 +343,16 @@ namespace System.Security.Cryptography
         private static void WriteUncompressedPublicKey(in ECParameters ecParameters, AsnWriter writer)
         {
             int publicKeyLength = ecParameters.Q.X.Length * 2 + 1;
-            Span<byte> publicKeyBytes = stackalloc byte[0];
-            byte[] publicKeyRented = null;
-
-            if (publicKeyLength < 256)
-            {
-                publicKeyBytes = stackalloc byte[publicKeyLength];
-            }
-            else
-            {
-                publicKeyRented = ArrayPool<byte>.Shared.Rent(publicKeyLength);
-                publicKeyBytes = publicKeyRented.AsSpan(0, publicKeyLength);
-            }
 
-            try
-            {
-                publicKeyBytes[0] = 0x04;
-                ecParameters.Q.X.AsSpan().CopyTo(publicKeyBytes.Slice(1));
-                ecParameters.Q.Y.AsSpan().CopyTo(publicKeyBytes.Slice(1 + ecParameters.Q.X.Length));
-
-                writer.WriteBitString(publicKeyBytes);
-            }
-            finally
-            {
-                CryptographicOperations.ZeroMemory(publicKeyBytes);
-
-                if (publicKeyRented != null)
+            writer.WriteBitString(
+                publicKeyLength,
+                ecParameters,
+                (publicKeyBytes, ecParams) =>
                 {
-                    ArrayPool<byte>.Shared.Return(publicKeyRented);
-                }
-            }
+                    publicKeyBytes[0] = 0x04;
+                    ecParams.Q.X.AsSpan().CopyTo(publicKeyBytes.Slice(1));
+                    ecParams.Q.Y.AsSpan().CopyTo(publicKeyBytes.Slice(1 + ecParams.Q.X.Length));
+                });
         }
 
         internal static AsnWriter WriteECPrivateKey(in ECParameters ecParameters)
index ff8a28e..83a9203 100644 (file)
@@ -210,7 +210,7 @@ namespace System.Security.Cryptography
 
             // No supported encryption algorithms produce more bytes of decryption output than there
             // were of decryption input.
-            byte[] decrypted = ArrayPool<byte>.Shared.Rent(epki.EncryptedData.Length);
+            byte[] decrypted = CryptoPool.Rent(epki.EncryptedData.Length);
             Memory<byte> decryptedMemory = decrypted;
 
             try
@@ -246,7 +246,7 @@ namespace System.Security.Cryptography
             finally
             {
                 CryptographicOperations.ZeroMemory(decryptedMemory.Span);
-                ArrayPool<byte>.Shared.Return(decrypted);
+                CryptoPool.Return(decrypted, clearSize: 0);
             }
         }
 
@@ -340,7 +340,7 @@ namespace System.Security.Cryptography
                 Span<byte> salt = stackalloc byte[16];
 
                 // We need at least one block size beyond the input data size.
-                encryptedRent = ArrayPool<byte>.Shared.Rent(
+                encryptedRent = CryptoPool.Rent(
                     checked(pkcs8Span.Length + (cipher.BlockSize / 8)));
 
                 RandomNumberGenerator.Fill(salt);
@@ -385,7 +385,7 @@ namespace System.Security.Cryptography
             finally
             {
                 CryptographicOperations.ZeroMemory(encryptedSpan);
-                ArrayPool<byte>.Shared.Return(encryptedRent);
+                CryptoPool.Return(encryptedRent, clearSize: 0);
 
                 writer?.Dispose();
                 cipher.Dispose();
@@ -428,7 +428,7 @@ namespace System.Security.Cryptography
 
             // No supported encryption algorithms produce more bytes of decryption output than there
             // were of decryption input.
-            byte[] decrypted = ArrayPool<byte>.Shared.Rent(epki.EncryptedData.Length);
+            byte[] decrypted = CryptoPool.Rent(epki.EncryptedData.Length);
 
             try
             {
@@ -445,7 +445,7 @@ namespace System.Security.Cryptography
             }
             catch (CryptographicException e)
             {
-                ArrayPool<byte>.Shared.Return(decrypted);
+                CryptoPool.Return(decrypted);
                 throw new CryptographicException(SR.Cryptography_Pkcs8_EncryptedReadFailed, e);
             }
         }
@@ -485,7 +485,7 @@ namespace System.Security.Cryptography
             finally
             {
                 CryptographicOperations.ZeroMemory(decrypted);
-                ArrayPool<byte>.Shared.Return(decrypted.Array);
+                CryptoPool.Return(decrypted.Array, clearSize: 0);
             }
         }
 
@@ -524,7 +524,7 @@ namespace System.Security.Cryptography
             finally
             {
                 CryptographicOperations.ZeroMemory(decrypted);
-                ArrayPool<byte>.Shared.Return(decrypted.Array);
+                CryptoPool.Return(decrypted.Array, clearSize: 0);
             }
         }
     }
index 2242bdb..44c4e81 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
@@ -15,8 +14,6 @@ namespace System.Security.Cryptography
     {
         internal const int IterationLimit = 600000;
 
-        private static ArrayPool<byte> ArrayPool => ArrayPool<byte>.Shared;
-
         private static CryptographicException AlgorithmKdfRequiresChars(string algId)
         {
             return new CryptographicException(SR.Cryptography_AlgKdfRequiresChars, algId);
@@ -178,7 +175,7 @@ namespace System.Security.Cryptography
 
                         if (byteCount > buf.Length)
                         {
-                            rented = ArrayPool.Rent(byteCount);
+                            rented = CryptoPool.Rent(byteCount);
                             buf = rented.AsSpan(0, byteCount);
                         }
                         else
@@ -213,7 +210,7 @@ namespace System.Security.Cryptography
 
                             if (rented != null)
                             {
-                                ArrayPool.Return(rented);
+                                CryptoPool.Return(rented, clearSize: 0);
                             }
                         }
                     }
@@ -306,7 +303,7 @@ namespace System.Security.Cryptography
             byte[] derivedKey;
             byte[] iv = cipher.IV;
 
-            byte[] sourceRent = ArrayPool.Rent(source.Length);
+            byte[] sourceRent = CryptoPool.Rent(source.Length);
             int keySizeBytes = cipher.KeySize / 8;
             int iterationCount = pbeParameters.IterationCount;
             HashAlgorithmName prf = pbeParameters.HashAlgorithm;
@@ -418,8 +415,7 @@ namespace System.Security.Cryptography
                         }
                         finally
                         {
-                            CryptographicOperations.ZeroMemory(sourceRent.AsSpan(0, source.Length));
-                            ArrayPool.Return(sourceRent);
+                            CryptoPool.Return(sourceRent, source.Length);
                         }
                     }
                 }
@@ -449,7 +445,7 @@ namespace System.Security.Cryptography
 
                 if (byteCount > buf.Length)
                 {
-                    rented = ArrayPool.Rent(byteCount);
+                    rented = CryptoPool.Rent(byteCount);
                     buf = rented.AsSpan(0, byteCount);
                 }
                 else
@@ -468,11 +464,21 @@ namespace System.Security.Cryptography
                     effectivePasswordBytes = buf;
                 }
 
-                return Pbes2Decrypt(
-                    algorithmParameters,
-                    effectivePasswordBytes,
-                    encryptedData,
-                    destination);
+                try
+                {
+                    return Pbes2Decrypt(
+                        algorithmParameters,
+                        effectivePasswordBytes,
+                        encryptedData,
+                        destination);
+                }
+                finally
+                {
+                    if (rented != null)
+                    {
+                        CryptoPool.Return(rented, buf.Length);
+                    }
+                }
             }
         }
 
@@ -882,8 +888,8 @@ namespace System.Security.Cryptography
             // When we define a Span-based decryption API this should be changed to use it.
             byte[] tmpKey = new byte[key.Length];
             byte[] tmpIv = new byte[iv.Length];
-            byte[] rentedEncryptedData = ArrayPool.Rent(encryptedData.Length);
-            byte[] rentedDestination = ArrayPool.Rent(destination.Length);
+            byte[] rentedEncryptedData = CryptoPool.Rent(encryptedData.Length);
+            byte[] rentedDestination = CryptoPool.Rent(destination.Length);
 
             // Keep all the arrays pinned so they can be correctly cleared
             fixed (byte* tmpKeyPtr = tmpKey)
@@ -927,11 +933,9 @@ namespace System.Security.Cryptography
                 {
                     CryptographicOperations.ZeroMemory(tmpKey);
                     CryptographicOperations.ZeroMemory(tmpIv);
-                    CryptographicOperations.ZeroMemory(rentedEncryptedData.AsSpan(0, encryptedData.Length));
-                    CryptographicOperations.ZeroMemory(rentedDestination.AsSpan(0, destination.Length));
 
-                    ArrayPool.Return(rentedEncryptedData);
-                    ArrayPool.Return(rentedDestination);
+                    CryptoPool.Return(rentedEncryptedData, encryptedData.Length);
+                    CryptoPool.Return(rentedDestination, destination.Length);
                 }
             }
         }
index fcb7d64..f7f7687 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Text;
@@ -137,7 +136,7 @@ namespace System.Security.Cryptography.Pkcs
             }
             else
             {
-                IRented = ArrayPool<byte>.Shared.Rent(ILen);
+                IRented = CryptoPool.Rent(ILen);
                 I = IRented.AsSpan(0, ILen);
             }
 
@@ -212,7 +211,7 @@ namespace System.Security.Cryptography.Pkcs
 
                 if (IRented != null)
                 {
-                    ArrayPool<byte>.Shared.Return(IRented);
+                    CryptoPool.Return(IRented, clearSize: 0);
                 }
 
                 hash.Dispose();
index 75444d4..9874132 100644 (file)
@@ -69,7 +69,7 @@ namespace System.Security.Cryptography
             {
                 if (encrypt && data.Length == 0)
                 {
-                    byte[] rented = ArrayPool<byte>.Shared.Rent(modulusSizeInBytes);
+                    byte[] rented = CryptoPool.Rent(modulusSizeInBytes);
                     Span<byte> paddedMessage = new Span<byte>(rented, 0, modulusSizeInBytes);
 
                     try
@@ -95,7 +95,7 @@ namespace System.Security.Cryptography
                     finally
                     {
                         CryptographicOperations.ZeroMemory(paddedMessage);
-                        ArrayPool<byte>.Shared.Return(rented);
+                        CryptoPool.Return(rented, clearSize: 0);
                     }
                 }
 
@@ -157,7 +157,7 @@ namespace System.Security.Cryptography
             {
                 if (encrypt && data.Length == 0)
                 {
-                    byte[] rented = ArrayPool<byte>.Shared.Rent(modulusSizeInBytes);
+                    byte[] rented = CryptoPool.Rent(modulusSizeInBytes);
                     Span<byte> paddedMessage = new Span<byte>(rented, 0, modulusSizeInBytes);
 
                     try
@@ -183,7 +183,7 @@ namespace System.Security.Cryptography
                     finally
                     {
                         CryptographicOperations.ZeroMemory(paddedMessage);
-                        ArrayPool<byte>.Shared.Return(rented);
+                        CryptoPool.Return(rented, clearSize: 0);
                     }
                 }
 
index e2617db..88092e2 100644 (file)
@@ -97,7 +97,7 @@ namespace System.Security.Cryptography
 
             try
             {
-                buf = ArrayPool<byte>.Shared.Rent(rsaSize);
+                buf = CryptoPool.Rent(rsaSize);
                 destination = new Span<byte>(buf, 0, rsaSize);
 
                 if (!TryDecrypt(key, data, destination, rsaPadding, oaepProcessor, out int bytesWritten))
@@ -111,7 +111,7 @@ namespace System.Security.Cryptography
             finally
             {
                 CryptographicOperations.ZeroMemory(destination);
-                ArrayPool<byte>.Shared.Return(buf);
+                CryptoPool.Return(buf, clearSize: 0);
             }
         }
 
@@ -216,7 +216,7 @@ namespace System.Security.Cryptography
 
             if (rsaPaddingProcessor != null)
             {
-                paddingBuf = ArrayPool<byte>.Shared.Rent(rsaSize);
+                paddingBuf = CryptoPool.Rent(rsaSize);
                 decryptBuf = paddingBuf;
             }
 
@@ -248,7 +248,7 @@ namespace System.Security.Cryptography
                     // DecryptBuf is paddingBuf if paddingBuf is not null, erase it before returning it.
                     // If paddingBuf IS null then decryptBuf was destination, and shouldn't be cleared.
                     CryptographicOperations.ZeroMemory(decryptBuf);
-                    ArrayPool<byte>.Shared.Return(paddingBuf);
+                    CryptoPool.Return(paddingBuf, clearSize: 0);
                 }
             }
         }
@@ -318,7 +318,7 @@ namespace System.Security.Cryptography
             if (rsaPaddingProcessor != null)
             {
                 Debug.Assert(rsaPadding == Interop.Crypto.RsaPadding.NoPadding);
-                byte[] rented = ArrayPool<byte>.Shared.Rent(rsaSize);
+                byte[] rented = CryptoPool.Rent(rsaSize);
                 Span<byte> tmp = new Span<byte>(rented, 0, rsaSize);
 
                 try
@@ -329,7 +329,7 @@ namespace System.Security.Cryptography
                 finally
                 {
                     CryptographicOperations.ZeroMemory(tmp);
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptoPool.Return(rented, clearSize: 0);
                 }
             }
             else
@@ -712,15 +712,14 @@ namespace System.Security.Cryptography
                     return false;
                 }
 
-                byte[] pssRented = ArrayPool<byte>.Shared.Rent(bytesRequired);
+                byte[] pssRented = CryptoPool.Rent(bytesRequired);
                 Span<byte> pssBytes = new Span<byte>(pssRented, 0, bytesRequired);
 
                 processor.EncodePss(hash, pssBytes, KeySize);
 
                 int ret = Interop.Crypto.RsaSignPrimitive(pssBytes, destination, rsa);
 
-                pssBytes.Clear();
-                ArrayPool<byte>.Shared.Return(pssRented);
+                CryptoPool.Return(pssRented, bytesRequired);
 
                 CheckReturn(ret);
 
@@ -787,7 +786,7 @@ namespace System.Security.Cryptography
                     return false;
                 }
 
-                byte[] rented = ArrayPool<byte>.Shared.Rent(requiredBytes);
+                byte[] rented = CryptoPool.Rent(requiredBytes);
                 Span<byte> unwrapped = new Span<byte>(rented, 0, requiredBytes);
 
                 try
@@ -804,8 +803,7 @@ namespace System.Security.Cryptography
                 }
                 finally
                 {
-                    unwrapped.Clear();
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptoPool.Return(rented, requiredBytes);
                 }
             }
 
index b6a0518..f93f015 100644 (file)
@@ -310,7 +310,7 @@ namespace System.Security.Cryptography
                         throw new CryptographicException(SR.Cryptography_InvalidPaddingMode);
                 }
 
-                byte[] rented = ArrayPool<byte>.Shared.Rent(rsaSize);
+                byte[] rented = CryptoPool.Rent(rsaSize);
                 Span<byte> tmp = new Span<byte>(rented, 0, rsaSize);
 
                 try
@@ -333,8 +333,8 @@ namespace System.Security.Cryptography
                 }
                 finally
                 {
-                    tmp.Clear();
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptographicOperations.ZeroMemory(tmp);
+                    CryptoPool.Return(rented, clearSize: 0);
                 }
             }
 
@@ -369,24 +369,23 @@ namespace System.Security.Cryptography
                 }
 
                 int maxOutputSize = RsaPaddingProcessor.BytesRequiredForBitCount(KeySize);
-                byte[] rented = ArrayPool<byte>.Shared.Rent(maxOutputSize);
-                Span<byte> contentsSpan = Span<byte>.Empty;
+                byte[] rented = CryptoPool.Rent(maxOutputSize);
+                int bytesWritten = 0;
 
                 try
                 {
-                    if (!TryDecrypt(keys.PrivateKey, data, rented, padding, out int bytesWritten))
+                    if (!TryDecrypt(keys.PrivateKey, data, rented, padding, out bytesWritten))
                     {
                         Debug.Fail($"TryDecrypt returned false with a modulus-sized destination");
                         throw new CryptographicException();
                     }
 
-                    contentsSpan = new Span<byte>(rented, 0, bytesWritten);
+                    Span<byte> contentsSpan = new Span<byte>(rented, 0, bytesWritten);
                     return contentsSpan.ToArray();
                 }
                 finally
                 {
-                    CryptographicOperations.ZeroMemory(contentsSpan);
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptoPool.Return(rented, bytesWritten);
                 }
             }
 
@@ -438,7 +437,7 @@ namespace System.Security.Cryptography
                 Debug.Assert(padding.Mode == RSAEncryptionPaddingMode.Oaep);
                 RsaPaddingProcessor processor = RsaPaddingProcessor.OpenProcessor(padding.OaepHashAlgorithm);
 
-                byte[] rented = ArrayPool<byte>.Shared.Rent(modulusSizeInBytes);
+                byte[] rented = CryptoPool.Rent(modulusSizeInBytes);
                 Span<byte> unpaddedData = Span<byte>.Empty;
 
                 try
@@ -456,7 +455,7 @@ namespace System.Security.Cryptography
                 finally
                 {
                     CryptographicOperations.ZeroMemory(unpaddedData);
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptoPool.Return(rented, clearSize: 0);
                 }
             }
 
@@ -585,7 +584,7 @@ namespace System.Security.Cryptography
                     return false;
                 }
 
-                byte[] rented = ArrayPool<byte>.Shared.Rent(rsaSize);
+                byte[] rented = CryptoPool.Rent(rsaSize);
                 Span<byte> buf = new Span<byte>(rented, 0, rsaSize);
                 processor.EncodePss(hash, buf, keySize);
 
@@ -596,7 +595,7 @@ namespace System.Security.Cryptography
                 finally
                 {
                     CryptographicOperations.ZeroMemory(buf);
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptoPool.Return(rented, clearSize: 0);
                 }
             }
 
@@ -653,7 +652,7 @@ namespace System.Security.Cryptography
                         return false;
                     }
 
-                    byte[] rented = ArrayPool<byte>.Shared.Rent(rsaSize);
+                    byte[] rented = CryptoPool.Rent(rsaSize);
                     Span<byte> unwrapped = new Span<byte>(rented, 0, rsaSize);
 
                     try
@@ -673,8 +672,8 @@ namespace System.Security.Cryptography
                     }
                     finally
                     {
-                        unwrapped.Clear();
-                        ArrayPool<byte>.Shared.Return(rented);
+                        CryptographicOperations.ZeroMemory(unwrapped);
+                        CryptoPool.Return(rented, clearSize: 0);
                     }
                 }
 
index 34de84e..2f54f03 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers;
 using System.Buffers.Binary;
 using System.Collections.Concurrent;
 using System.Diagnostics;
@@ -139,7 +138,7 @@ namespace System.Security.Cryptography
                     RandomNumberGenerator.Fill(seed);
 
                     // 2(e)
-                    dbMask = ArrayPool<byte>.Shared.Rent(db.Length);
+                    dbMask = CryptoPool.Rent(db.Length);
                     dbMaskSpan = new Span<byte>(dbMask, 0, db.Length);
                     Mgf1(hasher, seed, dbMaskSpan);
 
@@ -166,8 +165,8 @@ namespace System.Security.Cryptography
             {
                 if (dbMask != null)
                 {
-                    dbMaskSpan.Clear();
-                    ArrayPool<byte>.Shared.Return(dbMask);
+                    CryptographicOperations.ZeroMemory(dbMaskSpan);
+                    CryptoPool.Return(dbMask, clearSize: 0);
                 }
             }
         }
@@ -199,7 +198,7 @@ namespace System.Security.Cryptography
                 // seed = seedMask XOR maskedSeed
                 Xor(seed, maskedSeed);
 
-                byte[] tmp = ArrayPool<byte>.Shared.Rent(source.Length);
+                byte[] tmp = CryptoPool.Rent(source.Length);
 
                 try
                 {
@@ -261,8 +260,7 @@ namespace System.Security.Cryptography
                 }
                 finally
                 {
-                    Array.Clear(tmp, 0, source.Length);
-                    ArrayPool<byte>.Shared.Return(tmp);
+                    CryptoPool.Return(tmp, source.Length);
                 }
             }
         }
@@ -303,7 +301,7 @@ namespace System.Security.Cryptography
             Span<byte> hDest = em.Slice(dbLen, _hLen);
             em[emLen - 1] = 0xBC;
 
-            byte[] dbMaskRented = ArrayPool<byte>.Shared.Rent(dbLen);
+            byte[] dbMaskRented = CryptoPool.Rent(dbLen);
             Span<byte> dbMask = new Span<byte>(dbMaskRented, 0, dbLen);
 
             using (IncrementalHash hasher = IncrementalHash.CreateHash(_hashAlgorithmName))
@@ -348,8 +346,8 @@ namespace System.Security.Cryptography
                 }
             }
 
-            dbMask.Clear();
-            ArrayPool<byte>.Shared.Return(dbMaskRented);
+            CryptographicOperations.ZeroMemory(dbMask);
+            CryptoPool.Return(dbMaskRented, clearSize: 0);
         }
 
         internal bool VerifyPss(ReadOnlySpan<byte> mHash, ReadOnlySpan<byte> em, int keySize)
@@ -397,7 +395,7 @@ namespace System.Security.Cryptography
             }
 
             // 7. dbMask = MGF(H, emLen - hLen - 1)
-            byte[] dbMaskRented = ArrayPool<byte>.Shared.Rent(maskedDb.Length);
+            byte[] dbMaskRented = CryptoPool.Rent(maskedDb.Length);
             Span<byte> dbMask = new Span<byte>(dbMaskRented, 0, maskedDb.Length);
 
             try
@@ -457,8 +455,8 @@ namespace System.Security.Cryptography
             }
             finally
             {
-                dbMask.Clear();
-                ArrayPool<byte>.Shared.Return(dbMaskRented);
+                CryptographicOperations.ZeroMemory(dbMask);
+                CryptoPool.Return(dbMaskRented, clearSize: 0);
             }
         }
 
index 470e616..f625574 100644 (file)
@@ -54,18 +54,18 @@ namespace Internal.Cryptography
             // OpenSSL 1.1 does not allow partial overlap.
             if (input == output && inputOffset != outputOffset)
             {
-                byte[] tmp = ArrayPool<byte>.Shared.Rent(count);
+                byte[] tmp = CryptoPool.Rent(count);
+                int written = 0;
 
                 try
                 {
-                    int written = CipherUpdate(input, inputOffset, count, tmp, 0);
+                    written = CipherUpdate(input, inputOffset, count, tmp, 0);
                     Buffer.BlockCopy(tmp, 0, output, outputOffset, written);
                     return written;
                 }
                 finally
                 {
-                    CryptographicOperations.ZeroMemory(tmp.AsSpan(0, count));
-                    ArrayPool<byte>.Shared.Return(tmp);
+                    CryptoPool.Return(tmp, written);
                 }
             }
 
index 230884a..fa63b6e 100644 (file)
     <Compile Include="$(CommonPath)\System\Memory\PointerMemoryManager.cs">
       <Link>Common\System\Memory\PointerMemoryManager.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Security\Cryptography\CryptoPool.cs">
+      <Link>Common\System\Security\Cryptography\CryptoPool.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\System\Security\Cryptography\DSAKeyFormatHelper.cs">
       <Link>Common\System\Security\Cryptography\DSAKeyFormatHelper.cs</Link>
     </Compile>
   <ItemGroup>
     <None Include="@(AsnXml)" /> 
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index b620c8a..4f39f36 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
+using System.Diagnostics;
 using System.IO;
 using System.Numerics;
 using System.Runtime.InteropServices;
@@ -151,6 +152,7 @@ namespace System.Security.Cryptography
 
         protected virtual bool TryHashData(ReadOnlySpan<byte> data, Span<byte> destination, HashAlgorithmName hashAlgorithm, out int bytesWritten)
         {
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
             byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
             try
             {
@@ -199,23 +201,34 @@ namespace System.Security.Cryptography
                 throw HashAlgorithmNameNullOrEmpty();
             }
 
-            for (int i = 256; ; i = checked(i * 2))
+            // The biggest hash algorithm supported is SHA512, which is only 64 bytes (512 bits).
+            // So this should realistically never hit the fallback
+            // (it'd require a derived type to add support for a different hash algorithm, and that
+            // algorithm to have a large output.)
+            Span<byte> buf = stackalloc byte[128];
+            ReadOnlySpan<byte> hash = stackalloc byte[0];
+
+            if (TryHashData(data, buf, hashAlgorithm, out int hashLength))
+            {
+                hash = buf.Slice(0, hashLength);
+            }
+            else
             {
-                int hashLength = 0;
-                byte[] hash = ArrayPool<byte>.Shared.Rent(i);
+                // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
+                byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
                 try
                 {
-                    if (TryHashData(data, hash, hashAlgorithm, out hashLength))
-                    {
-                        return VerifySignature(new ReadOnlySpan<byte>(hash, 0, hashLength), signature);
-                    }
+                    data.CopyTo(array);
+                    hash = HashData(array, 0, data.Length, hashAlgorithm);
                 }
                 finally
                 {
-                    Array.Clear(hash, 0, hashLength);
-                    ArrayPool<byte>.Shared.Return(hash);
+                    Array.Clear(array, 0, data.Length);
+                    ArrayPool<byte>.Shared.Return(array);
                 }
             }
+
+            return VerifySignature(hash, signature);
         }
 
         public virtual bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
index fa8e0e2..2e1fae7 100644 (file)
@@ -150,23 +150,34 @@ namespace System.Security.Cryptography
                 throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
             }
 
-            for (int i = 256; ; i = checked(i * 2))
+            // The biggest hash algorithm supported is SHA512, which is only 64 bytes (512 bits).
+            // So this should realistically never hit the fallback
+            // (it'd require a derived type to add support for a different hash algorithm, and that
+            // algorithm to have a large output.)
+            Span<byte> buf = stackalloc byte[128];
+            ReadOnlySpan<byte> hash = stackalloc byte[0];
+
+            if (TryHashData(data, buf, hashAlgorithm, out int hashLength))
             {
-                int hashLength = 0;
-                byte[] hash = ArrayPool<byte>.Shared.Rent(i);
+                hash = buf.Slice(0, hashLength);
+            }
+            else
+            {
+                // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
+                byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
                 try
                 {
-                    if (TryHashData(data, hash, hashAlgorithm, out hashLength))
-                    {
-                        return VerifyHash(new ReadOnlySpan<byte>(hash, 0, hashLength), signature);
-                    }
+                    data.CopyTo(array);
+                    hash = HashData(array, 0, data.Length, hashAlgorithm);
                 }
                 finally
                 {
-                    Array.Clear(hash, 0, hashLength);
-                    ArrayPool<byte>.Shared.Return(hash);
+                    Array.Clear(array, 0, data.Length);
+                    ArrayPool<byte>.Shared.Return(array);
                 }
             }
+
+            return VerifyHash(hash, signature);
         }
 
         public bool VerifyData(Stream data, byte[] signature, HashAlgorithmName hashAlgorithm)
@@ -200,6 +211,7 @@ namespace System.Security.Cryptography
 
         protected virtual bool TryHashData(ReadOnlySpan<byte> data, Span<byte> destination, HashAlgorithmName hashAlgorithm, out int bytesWritten)
         {
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
             byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
             try
             {
index f2bbc55..3be56f9 100644 (file)
@@ -92,6 +92,7 @@ namespace System.Security.Cryptography
         protected virtual bool TryHashData(ReadOnlySpan<byte> data, Span<byte> destination, HashAlgorithmName hashAlgorithm, out int bytesWritten)
         {
             byte[] result;
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
             byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
             try
             {
@@ -270,7 +271,7 @@ namespace System.Security.Cryptography
             for (int i = 256; ; i = checked(i * 2))
             {
                 int hashLength = 0;
-                byte[] hash = ArrayPool<byte>.Shared.Rent(i);
+                byte[] hash = CryptoPool.Rent(i);
                 try
                 {
                     if (TryHashData(data, hash, hashAlgorithm, out hashLength))
@@ -280,8 +281,7 @@ namespace System.Security.Cryptography
                 }
                 finally
                 {
-                    Array.Clear(hash, 0, hashLength);
-                    ArrayPool<byte>.Shared.Return(hash);
+                    CryptoPool.Return(hash, hashLength);
                 }
             }
         }
@@ -329,7 +329,7 @@ namespace System.Security.Cryptography
 
             while (true)
             {
-                byte[] rented = ArrayPool<byte>.Shared.Rent(rentSize);
+                byte[] rented = CryptoPool.Rent(rentSize);
                 rentSize = rented.Length;
                 int pkcs1Size = 0;
 
@@ -350,8 +350,7 @@ namespace System.Security.Cryptography
                     }
                     finally
                     {
-                        CryptographicOperations.ZeroMemory(rented.AsSpan(0, pkcs1Size));
-                        ArrayPool<byte>.Shared.Return(rented);
+                        CryptoPool.Return(rented, pkcs1Size);
                     }
                 }
             }
@@ -377,7 +376,7 @@ namespace System.Security.Cryptography
 
             while (true)
             {
-                byte[] rented = ArrayPool<byte>.Shared.Rent(rentSize);
+                byte[] rented = CryptoPool.Rent(rentSize);
                 rentSize = rented.Length;
                 int pkcs1Size = 0;
 
@@ -395,8 +394,7 @@ namespace System.Security.Cryptography
                     }
                     finally
                     {
-                        CryptographicOperations.ZeroMemory(rented.AsSpan(0, pkcs1Size));
-                        ArrayPool<byte>.Shared.Return(rented);
+                        CryptoPool.Return(rented, pkcs1Size);
                     }
                 }
             }
index e28f0dd..1986d56 100644 (file)
@@ -54,6 +54,7 @@ namespace System.Security.Cryptography
 
         public virtual void GetBytes(Span<byte> data)
         {
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
             byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
             try
             {
@@ -76,6 +77,7 @@ namespace System.Security.Cryptography
 
         public virtual void GetNonZeroBytes(Span<byte> data)
         {
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
             byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
             try
             {
index 2eecd85..e72eb83 100644 (file)
@@ -43,7 +43,8 @@ namespace System.Security.Cryptography
             if (password == null)
                 throw new NullReferenceException();  // This "should" be ArgumentNullException but for compat, we throw NullReferenceException.
 
-            _salt = salt.CloneByteArray();
+            _salt = new byte[salt.Length + sizeof(uint)];
+            salt.AsSpan().CopyTo(_salt);
             _iterations = (uint)iterations;
             _password = password.CloneByteArray();
             HashAlgorithm = hashAlgorithm;
@@ -88,7 +89,9 @@ namespace System.Security.Cryptography
             if (iterations <= 0)
                 throw new ArgumentOutOfRangeException(nameof(iterations), SR.ArgumentOutOfRange_NeedPosNum);
 
-            _salt = Helpers.GenerateRandom(saltSize);
+            _salt = new byte[saltSize + sizeof(uint)];
+            RandomNumberGenerator.Fill(_salt.AsSpan(0, saltSize));
+
             _iterations = (uint)iterations;
             _password = Encoding.UTF8.GetBytes(password);
             HashAlgorithm = hashAlgorithm;
@@ -119,7 +122,7 @@ namespace System.Security.Cryptography
         {
             get
             {
-                return _salt.CloneByteArray();
+                return _salt.AsSpan(0, _salt.Length - sizeof(uint)).ToArray();
             }
 
             set
@@ -128,7 +131,9 @@ namespace System.Security.Cryptography
                     throw new ArgumentNullException(nameof(value));
                 if (value.Length < MinimumSaltSize)
                     throw new ArgumentException(SR.Cryptography_PasswordDerivedBytes_FewBytesSalt);
-                _salt = value.CloneByteArray();
+
+                _salt = new byte[value.Length + sizeof(uint)];
+                value.AsSpan().CopyTo(_salt);
                 Initialize();
             }
         }
@@ -183,19 +188,18 @@ namespace System.Security.Cryptography
 
             while (offset < cb)
             {
-                byte[] T_block = Func();
+                Func();
                 int remainder = cb - offset;
-                if (remainder > _blockSize)
+                if (remainder >= _blockSize)
                 {
-                    Buffer.BlockCopy(T_block, 0, password, offset, _blockSize);
+                    Buffer.BlockCopy(_buffer, 0, password, offset, _blockSize);
                     offset += _blockSize;
                 }
                 else
                 {
-                    Buffer.BlockCopy(T_block, 0, password, offset, remainder);
-                    offset += remainder;
-                    Buffer.BlockCopy(T_block, remainder, _buffer, _startIndex, _blockSize - remainder);
-                    _endIndex += (_blockSize - remainder);
+                    Buffer.BlockCopy(_buffer, 0, password, offset, remainder);
+                    _startIndex = remainder;
+                    _endIndex = _buffer.Length;
                     return password;
                 }
             }
@@ -252,43 +256,40 @@ namespace System.Security.Cryptography
         // This function is defined as follows:
         // Func (S, i) = HMAC(S || i) ^ HMAC2(S || i) ^ ... ^ HMAC(iterations) (S || i) 
         // where i is the block number.
-        private byte[] Func()
+        private void Func()
         {
-            byte[] temp = new byte[_salt.Length + sizeof(uint)];
-            Buffer.BlockCopy(_salt, 0, temp, 0, _salt.Length);
-            Helpers.WriteInt(_block, temp, _salt.Length);
+            Helpers.WriteInt(_block, _salt, _salt.Length - sizeof(uint));
+            Debug.Assert(_blockSize == _buffer.Length);
 
-            byte[] ui = ArrayPool<byte>.Shared.Rent(_blockSize);
-            try
-            {
-                Span<byte> uiSpan = new Span<byte>(ui, 0, _blockSize);
+            // The biggest _blockSize we have is from SHA512, which is 64 bytes.
+            // Since we have a closed set of supported hash algorithms (OpenHmac())
+            // we can know this always fits.
+            // 
+            Span<byte> uiSpan = stackalloc byte[64];
+            uiSpan = uiSpan.Slice(0, _blockSize);
 
-                if (!_hmac.TryComputeHash(temp, uiSpan, out int bytesWritten) || bytesWritten != _blockSize)
-                    throw new CryptographicException();
+            if (!_hmac.TryComputeHash(_salt, uiSpan, out int bytesWritten) || bytesWritten != _blockSize)
+            {
+                throw new CryptographicException();
+            }
 
-                byte[] ret = new byte[_blockSize];
-                uiSpan.CopyTo(ret);
+            uiSpan.CopyTo(_buffer);
 
-                for (int i = 2; i <= _iterations; i++)
+            for (int i = 2; i <= _iterations; i++)
+            {
+                if (!_hmac.TryComputeHash(uiSpan, uiSpan, out bytesWritten) || bytesWritten != _blockSize)
                 {
-                    if (!_hmac.TryComputeHash(uiSpan, uiSpan, out bytesWritten) || bytesWritten != _blockSize)
-                        throw new CryptographicException();
-
-                    for (int j = 0; j < _blockSize; j++)
-                    {
-                        ret[j] ^= ui[j];
-                    }
+                    throw new CryptographicException();
                 }
 
-                // increment the block count.
-                _block++;
-                return ret;
-            }
-            finally
-            {
-                Array.Clear(ui, 0, _blockSize);
-                ArrayPool<byte>.Shared.Return(ui);
+                for (int j = _buffer.Length - 1; j >= 0; j--)
+                {
+                    _buffer[j] ^= uiSpan[j];
+                }
             }
+
+            // increment the block count.
+            _block++;
         }
     }
 }
index 1b240bf..224929c 100644 (file)
@@ -214,14 +214,23 @@ namespace System.Security.Cryptography.DeriveBytesTests
             Assert.NotEqual(first, second);
         }
 
-        [Fact]
-        public static void GetBytes_StreamLike()
+        [Theory]
+        [InlineData(2)]
+        [InlineData(5)]
+        [InlineData(10)]
+        [InlineData(16)]
+        [InlineData(20)]
+        [InlineData(25)]
+        [InlineData(32)]
+        [InlineData(40)]
+        [InlineData(192)]
+        public static void GetBytes_StreamLike(int size)
         {
             byte[] first;
 
             using (var deriveBytes = new Rfc2898DeriveBytes(TestPassword, s_testSalt))
             {
-                first = deriveBytes.GetBytes(32);
+                first = deriveBytes.GetBytes(size);
             }
 
             byte[] second = new byte[first.Length];
@@ -238,7 +247,40 @@ namespace System.Security.Cryptography.DeriveBytesTests
 
             Assert.Equal(first, second);
         }
-        
+
+        [Theory]
+        [InlineData(2)]
+        [InlineData(5)]
+        [InlineData(10)]
+        [InlineData(16)]
+        [InlineData(20)]
+        [InlineData(25)]
+        [InlineData(32)]
+        [InlineData(40)]
+        [InlineData(192)]
+        public static void GetBytes_StreamLike_OneAtATime(int size)
+        {
+            byte[] first;
+
+            using (var deriveBytes = new Rfc2898DeriveBytes(TestPasswordB, s_testSaltB))
+            {
+                first = deriveBytes.GetBytes(size);
+            }
+
+            byte[] second = new byte[first.Length];
+
+            // Reset
+            using (var deriveBytes = new Rfc2898DeriveBytes(TestPasswordB, s_testSaltB))
+            {
+                for (int i = 0; i < second.Length; i++)
+                {
+                    second[i] = deriveBytes.GetBytes(1)[0];
+                }
+            }
+
+            Assert.Equal(first, second);
+        }
+
         [Fact]
         public static void GetBytes_KnownValues_1()
         {
index 5d151bb..581bd6b 100644 (file)
     <Compile Include="$(CommonPath)\System\Memory\PointerMemoryManager.cs">
       <Link>Common\System\Memory\PointerMemoryManager.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Security\Cryptography\CryptoPool.cs">
+      <Link>Common\System\Security\Cryptography\CryptoPool.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\System\Security\Cryptography\CngPkcs8.cs">
       <Link>Common\System\Security\Cryptography\CngPkcs8.cs</Link>
     </Compile>
index 882a8c5..8b6ffd9 100644 (file)
@@ -98,6 +98,9 @@
     <Compile Include="$(CommonPath)\System\Memory\PointerMemoryManager.cs">
       <Link>Common\System\Memory\PointerMemoryManager.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Security\Cryptography\CryptoPool.cs">
+      <Link>Common\System\Security\Cryptography\CryptoPool.cs</Link>
+    </Compile>
     <AsnXml Include="$(CommonPath)\System\Security\Cryptography\Asn1\DirectoryStringAsn.xml">
       <Link>Common\System\Security\Cryptography\Asn1\DirectoryStringAsn.xml</Link>
     </AsnXml>
index aa90a02..ba32fda 100644 (file)
@@ -6,6 +6,9 @@
   </PropertyGroup>
   <Import Project="$(CommonPath)\System\Security\Cryptography\Asn1Reader\System.Security.Cryptography.Asn1Reader.Shared.projitems" />
   <ItemGroup>
+    <Compile Include="$(CommonPath)\System\Security\Cryptography\CryptoPool.cs">
+      <Link>Common\System\Security\Cryptography\CryptoPool.cs</Link>
+    </Compile>
     <Compile Include="Asn1\Reader\Asn1ReaderTests.cs" />
     <Compile Include="Asn1\Reader\ComprehensiveReadTests.cs" />
     <Compile Include="Asn1\Reader\ParseTag.cs" />
@@ -58,4 +61,4 @@
       <Link>Common\System\Memory\PointerMemoryManager.cs</Link>
     </Compile>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index c28cda4..fc429bf 100644 (file)
     <Compile Include="$(CommonPath)\System\Memory\PointerMemoryManager.cs">
       <Link>Common\System\Memory\PointerMemoryManager.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Security\Cryptography\CryptoPool.cs">
+      <Link>Common\System\Security\Cryptography\CryptoPool.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\System\Security\Cryptography\DSAOpenSsl.cs">
       <Link>Common\System\Security\Cryptography\DSAOpenSsl.cs</Link>
     </Compile>
index 47b2f91..489373a 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Buffers;
 using System.Diagnostics;
 using System.Security.Cryptography;
 using System.Security.Cryptography.Asn1;
@@ -46,7 +45,7 @@ namespace Internal.Cryptography.Pal.AnyOS
                     }
                     else
                     {
-                        poolBytes = ArrayPool<byte>.Shared.Rent(reader.PeekContentBytes().Length);
+                        poolBytes = CryptoPool.Rent(reader.PeekContentBytes().Length);
 
                         if (!reader.TryCopyOctetStringBytes(poolBytes, out bytesWritten))
                         {
@@ -70,8 +69,7 @@ namespace Internal.Cryptography.Pal.AnyOS
             {
                 if (poolBytes != null)
                 {
-                    Array.Clear(poolBytes, 0, data.Length);
-                    ArrayPool<byte>.Shared.Return(poolBytes);
+                    CryptoPool.Return(poolBytes, data.Length);
                 }
             }
         }
index 0a80301..d840024 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Buffers;
 using System.Diagnostics;
 using System.Security.Cryptography;
 using System.Security.Cryptography.Asn1;
@@ -130,7 +129,7 @@ namespace Internal.Cryptography.Pal.AnyOS
                         }
                         else
                         {
-                            tmp = ArrayPool<byte>.Shared.Rent(decrypted.Length);
+                            tmp = CryptoPool.Rent(decrypted.Length);
 
                             if (reader.TryCopyOctetStringBytes(tmp, out int written))
                             {
@@ -154,7 +153,7 @@ namespace Internal.Cryptography.Pal.AnyOS
                         if (tmp != null)
                         {
                             // Already cleared
-                            ArrayPool<byte>.Shared.Return(tmp);
+                            CryptoPool.Return(tmp, clearSize: 0);
                         }
                     }
                 }
@@ -194,7 +193,7 @@ namespace Internal.Cryptography.Pal.AnyOS
             {
                 exception = null;
                 int encryptedContentLength = encryptedContent.Length;
-                byte[] encryptedContentArray = ArrayPool<byte>.Shared.Rent(encryptedContentLength);
+                byte[] encryptedContentArray = CryptoPool.Rent(encryptedContentLength);
 
                 try
                 {
@@ -203,6 +202,10 @@ namespace Internal.Cryptography.Pal.AnyOS
                     using (SymmetricAlgorithm alg = OpenAlgorithm(contentEncryptionAlgorithm))
                     using (ICryptoTransform decryptor = alg.CreateDecryptor(cek, alg.IV))
                     {
+                        // If we extend this library to accept additional algorithm providers
+                        // then a different array pool needs to be used.
+                        Debug.Assert(alg.GetType().Assembly == typeof(Aes).Assembly);
+
                         return decryptor.OneShot(
                             encryptedContentArray,
                             0,
@@ -216,8 +219,7 @@ namespace Internal.Cryptography.Pal.AnyOS
                 }
                 finally
                 {
-                    Array.Clear(encryptedContentArray, 0, encryptedContentLength);
-                    ArrayPool<byte>.Shared.Return(encryptedContentArray);
+                    CryptoPool.Return(encryptedContentArray, encryptedContentLength);
                     encryptedContentArray = null;
                 }
             }
index 59625e4..a1e5968 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Buffers;
 using System.Diagnostics;
 using System.Security.Cryptography;
 using System.Security.Cryptography.Pkcs;
@@ -196,7 +195,7 @@ namespace Internal.Cryptography.Pal.AnyOS
 
             try
             {
-                cek = ArrayPool<byte>.Shared.Rent(privateKey.KeySize / 8);
+                cek = CryptoPool.Rent(privateKey.KeySize / 8);
 
                 if (!privateKey.TryDecrypt(encryptedKey, cek, encryptionPadding, out cekLength))
                 {
@@ -217,8 +216,7 @@ namespace Internal.Cryptography.Pal.AnyOS
             {
                 if (cek != null)
                 {
-                    Array.Clear(cek, 0, cekLength);
-                    ArrayPool<byte>.Shared.Return(cek);
+                    CryptoPool.Return(cek, cekLength);
                 }
             }
 #else
index 1133625..252ee48 100644 (file)
@@ -420,8 +420,7 @@ namespace Internal.Cryptography.Pal.Windows
 
             if (rented != null)
             {
-                Array.Clear(rented, 0, maxClear);
-                ArrayPool<byte>.Shared.Return(rented);
+                CryptoPool.Return(rented, maxClear);
             }
 
             return new CspParameters(provType)
@@ -445,15 +444,12 @@ namespace Internal.Cryptography.Pal.Windows
             {
                 if (len > buf.Length)
                 {
-                    ArrayPool<byte> pool = ArrayPool<byte>.Shared;
-
                     if (rented != null)
                     {
-                        Array.Clear(rented, 0, clearLen);
-                        pool.Return(rented);
+                        CryptoPool.Return(rented, clearLen);
                     }
 
-                    rented = pool.Rent(len);
+                    rented = CryptoPool.Rent(len);
                     buf = rented;
                     len = rented.Length;
                 }
index d34768d..d045a33 100644 (file)
     <Compile Include="$(CommonPath)\System\Memory\PointerMemoryManager.cs">
       <Link>Common\System\Memory\PointerMemoryManager.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Security\Cryptography\CryptoPool.cs">
+      <Link>Common\System\Security\Cryptography\CryptoPool.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\System\Security\Cryptography\Oids.cs">
       <Link>Common\System\Security\Cryptography\Oids.cs</Link>
     </Compile>
index 1ed7a6f..62cf6bd 100644 (file)
@@ -69,8 +69,7 @@ namespace System.Security.Cryptography.Pkcs
                 int bufSize = 2 * dsaParameters.Q.Length;
 
 #if netcoreapp
-                ArrayPool<byte> pool = ArrayPool<byte>.Shared;
-                byte[] rented = pool.Rent(bufSize);
+                byte[] rented = CryptoPool.Rent(bufSize);
                 Span<byte> ieee = new Span<byte>(rented, 0, bufSize);
 
                 try
@@ -88,8 +87,7 @@ namespace System.Security.Cryptography.Pkcs
                 }
                 finally
                 {
-                    ieee.Clear();
-                    pool.Return(rented);
+                    CryptoPool.Return(rented, bufSize);
                 }
 #endif
             }
@@ -136,9 +134,8 @@ namespace System.Security.Cryptography.Pkcs
                 signatureAlgorithm = new Oid(oidValue, oidValue);
 
 #if netcoreapp
-                ArrayPool<byte> pool = ArrayPool<byte>.Shared;
                 // The Q size cannot be bigger than the KeySize.
-                byte[] rented = pool.Rent(dsa.KeySize / 8);
+                byte[] rented = CryptoPool.Rent(dsa.KeySize / 8);
                 int bytesWritten = 0;
 
                 try
@@ -160,8 +157,7 @@ namespace System.Security.Cryptography.Pkcs
                 }
                 finally
                 {
-                    Array.Clear(rented, 0, bytesWritten);
-                    pool.Return(rented);
+                    CryptoPool.Return(rented, bytesWritten);
                 }
 
                 signatureValue = null;
index fdce23c..827e101 100644 (file)
@@ -74,8 +74,7 @@ namespace System.Security.Cryptography.Pkcs
                 }
 
 #if netcoreapp
-                ArrayPool<byte> pool = ArrayPool<byte>.Shared;
-                byte[] rented = pool.Rent(bufSize);
+                byte[] rented = CryptoPool.Rent(bufSize);
                 Span<byte> ieee = new Span<byte>(rented, 0, bufSize);
 
                 try
@@ -93,8 +92,7 @@ namespace System.Security.Cryptography.Pkcs
                 }
                 finally
                 {
-                    ieee.Clear();
-                    pool.Return(rented);
+                    CryptoPool.Return(rented, bufSize);
                 }
 #endif
             }
@@ -141,8 +139,6 @@ namespace System.Security.Cryptography.Pkcs
                 signatureAlgorithm = new Oid(oidValue, oidValue);
 
 #if netcoreapp
-                ArrayPool<byte> pool = ArrayPool<byte>.Shared;
-
                 int bufSize;
                 checked
                 {
@@ -151,7 +147,7 @@ namespace System.Security.Cryptography.Pkcs
                     bufSize = 2 * fieldSize;
                 }
 
-                byte[] rented = pool.Rent(bufSize);
+                byte[] rented = CryptoPool.Rent(bufSize);
                 int bytesWritten = 0;
 
                 try
@@ -173,8 +169,7 @@ namespace System.Security.Cryptography.Pkcs
                 }
                 finally
                 {
-                    Array.Clear(rented, 0, bytesWritten);
-                    pool.Return(rented);
+                    CryptoPool.Return(rented, bytesWritten);
                 }
 #endif
 
index a5f5739..7926dff 100644 (file)
@@ -184,13 +184,13 @@ namespace System.Security.Cryptography.Pkcs
 
                     ReadOnlySpan<byte> encodedSpan = contentsWriter.EncodeAsSpan();
 
-                    rentedAuthSafe = ArrayPool<byte>.Shared.Rent(encodedSpan.Length);
+                    rentedAuthSafe = CryptoPool.Rent(encodedSpan.Length);
                     encodedSpan.CopyTo(rentedAuthSafe);
                     authSafeSpan = rentedAuthSafe.AsSpan(0, encodedSpan.Length);
 
                     // Get an array of the proper size for the hash.
                     byte[] macKey = hasher.GetHashAndReset();
-                    rentedMac = ArrayPool<byte>.Shared.Rent(macKey.Length);
+                    rentedMac = CryptoPool.Rent(macKey.Length);
                     macSpan = rentedMac.AsSpan(0, macKey.Length);
 
                     // Since the biggest supported hash is SHA-2-512 (64 bytes), the
@@ -290,12 +290,14 @@ namespace System.Security.Cryptography.Pkcs
 
                 if (rentedMac != null)
                 {
-                    ArrayPool<byte>.Shared.Return(rentedMac);
+                    // Already cleared
+                    CryptoPool.Return(rentedMac, clearSize: 0);
                 }
 
                 if (rentedAuthSafe != null)
                 {
-                    ArrayPool<byte>.Shared.Return(rentedAuthSafe);
+                    // Already cleared
+                    CryptoPool.Return(rentedAuthSafe, clearSize: 0);
                 }
             }
         }
index f094642..895dd66 100644 (file)
@@ -366,7 +366,7 @@ namespace System.Security.Cryptography.Pkcs
                     out bool isPkcs12);
 
                 int cipherBlockBytes = cipher.BlockSize / 8;
-                byte[] encryptedRent = ArrayPool<byte>.Shared.Rent(contentsSpan.Length + cipherBlockBytes);
+                byte[] encryptedRent = CryptoPool.Rent(contentsSpan.Length + cipherBlockBytes);
                 Span<byte> encryptedSpan = Span<byte>.Empty;
                 Span<byte> iv = stackalloc byte[cipherBlockBytes];
                 Span<byte> salt = stackalloc byte[16];
@@ -424,7 +424,7 @@ namespace System.Security.Cryptography.Pkcs
                 finally
                 {
                     CryptographicOperations.ZeroMemory(encryptedSpan);
-                    ArrayPool<byte>.Shared.Return(encryptedRent);
+                    CryptoPool.Return(encryptedRent, clearSize: 0);
                     writer?.Dispose();
                 }
             }
index 5587f72..7b71442 100644 (file)
@@ -197,7 +197,7 @@ namespace System.Security.Cryptography.Pkcs
             finally
             {
                 CryptographicOperations.ZeroMemory(decryptedMemory.Span);
-                ArrayPool<byte>.Shared.Return(decrypted.Array);
+                CryptoPool.Return(decrypted.Array, clearSize: 0);
             }
         }
 
@@ -229,7 +229,7 @@ namespace System.Security.Cryptography.Pkcs
             finally
             {
                 CryptographicOperations.ZeroMemory(decryptedMemory.Span);
-                ArrayPool<byte>.Shared.Return(decrypted.Array);
+                CryptoPool.Return(decrypted.Array, clearSize: 0);
             }
         }
 
index 330bbf4..1e80dd0 100644 (file)
@@ -243,7 +243,7 @@ namespace System.Security.Cryptography.Pkcs
                     return inner;
                 }
 
-                rented = ArrayPool<byte>.Shared.Rent(wrappedContent.Length);
+                rented = CryptoPool.Rent(wrappedContent.Length);
 
                 if (!reader.TryCopyOctetStringBytes(rented, out bytesWritten))
                 {
@@ -260,8 +260,7 @@ namespace System.Security.Cryptography.Pkcs
             {
                 if (rented != null)
                 {
-                    rented.AsSpan(0, bytesWritten).Clear();
-                    ArrayPool<byte>.Shared.Return(rented);
+                    CryptoPool.Return(rented, bytesWritten);
                 }
             }
 
index 9a76ba0..834a575 100644 (file)
@@ -30,6 +30,9 @@
     <Compile Include="$(CommonPath)\Internal\Cryptography\Helpers.cs">
       <Link>Internal\Cryptography\Helpers.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Security\Cryptography\CryptoPool.cs">
+      <Link>Common\System\Security\Cryptography\CryptoPool.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\System\Security\Cryptography\KeySizeHelpers.cs">
       <Link>Common\System\Security\Cryptography\KeySizeHelpers.cs</Link>
     </Compile>
   <ItemGroup>
     <Reference Include="System.Buffers" />
     <Reference Include="System.Diagnostics.Debug" />
+    <Reference Include="System.Memory" />
     <Reference Include="System.Resources.ResourceManager" />
     <Reference Include="System.Runtime" />
     <Reference Include="System.Threading" />
     <Reference Include="System.Threading.Tasks" />
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index 83c158f..359bd68 100644 (file)
@@ -2,8 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers;
-
 namespace System.Security.Cryptography
 {
     public abstract class AsymmetricAlgorithm : IDisposable
@@ -180,28 +178,23 @@ namespace System.Security.Cryptography
 
             while (true)
             {
-                Span<byte> writtenSpan = Span<byte>.Empty;
-                byte[] buf = ArrayPool<byte>.Shared.Rent(bufSize);
+                byte[] buf = CryptoPool.Rent(bufSize);
+                int bytesWritten = 0;
                 bufSize = buf.Length;
 
                 fixed (byte* bufPtr = buf)
                 {
                     try
                     {
-                        if (exporter(password, pbeParameters, buf, out int bytesWritten))
+                        if (exporter(password, pbeParameters, buf, out bytesWritten))
                         {
-                            writtenSpan = new Span<byte>(buf, 0, bytesWritten);
+                            Span<byte> writtenSpan = new Span<byte>(buf, 0, bytesWritten);
                             return writtenSpan.ToArray();
                         }
                     }
                     finally
                     {
-                        if (writtenSpan.Length > 0)
-                        {
-                            CryptographicOperations.ZeroMemory(writtenSpan);
-                        }
-
-                        ArrayPool<byte>.Shared.Return(buf);
+                        CryptoPool.Return(buf, bytesWritten);
                     }
 
                     bufSize = checked(bufSize * 2);
@@ -215,28 +208,23 @@ namespace System.Security.Cryptography
 
             while (true)
             {
-                Span<byte> writtenSpan = Span<byte>.Empty;
-                byte[] buf = ArrayPool<byte>.Shared.Rent(bufSize);
+                byte[] buf = CryptoPool.Rent(bufSize);
+                int bytesWritten = 0;
                 bufSize = buf.Length;
 
                 fixed (byte* bufPtr = buf)
                 {
                     try
                     {
-                        if (exporter(buf, out int bytesWritten))
+                        if (exporter(buf, out bytesWritten))
                         {
-                            writtenSpan = new Span<byte>(buf, 0, bytesWritten);
+                            Span<byte> writtenSpan = new Span<byte>(buf, 0, bytesWritten);
                             return writtenSpan.ToArray();
                         }
                     }
                     finally
                     {
-                        if (writtenSpan.Length > 0)
-                        {
-                            CryptographicOperations.ZeroMemory(writtenSpan);
-                        }
-
-                        ArrayPool<byte>.Shared.Return(buf);
+                        CryptoPool.Return(buf, bytesWritten);
                     }
 
                     bufSize = checked(bufSize * 2);
index efa6cfd..bdb6ec6 100644 (file)
@@ -318,6 +318,8 @@ namespace System.Security.Cryptography
             if (blocksToProcess > 1 && _transform.CanTransformMultipleBlocks)
             {
                 int numWholeBlocksInBytes = blocksToProcess * _inputBlockSize;
+
+                // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
                 byte[] tempInputBuffer = ArrayPool<byte>.Shared.Rent(numWholeBlocksInBytes);
                 byte[] tempOutputBuffer = null;
 
@@ -354,6 +356,7 @@ namespace System.Security.Cryptography
                             Buffer.BlockCopy(tempInputBuffer, numWholeReadBlocksInBytes, _inputBuffer, 0, numIgnoredBytes);
                         }
 
+                        // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
                         tempOutputBuffer = ArrayPool<byte>.Shared.Rent(numWholeReadBlocks * _outputBlockSize);
                         numOutputBytes = _transform.TransformBlock(tempInputBuffer, 0, numWholeReadBlocksInBytes, tempOutputBuffer, 0);
                         Buffer.BlockCopy(tempOutputBuffer, 0, buffer, currentOutputIndex, numOutputBytes);
@@ -366,21 +369,30 @@ namespace System.Security.Cryptography
                         bytesToDeliver -= numOutputBytes;
                         currentOutputIndex += numOutputBytes;
                     }
+
+                    CryptographicOperations.ZeroMemory(new Span<byte>(tempInputBuffer, 0, numWholeBlocksInBytes));
+                    ArrayPool<byte>.Shared.Return(tempInputBuffer);
+                    tempInputBuffer = null;
                 }
-                finally
+                catch
                 {
                     // If we rented and then an exception happened we don't know how much was written to,
-                    // clear the whole thing and return it.
+                    // clear the whole thing and let it get reclaimed by the GC.
                     if (tempOutputBuffer != null)
                     {
                         CryptographicOperations.ZeroMemory(tempOutputBuffer);
-                        ArrayPool<byte>.Shared.Return(tempOutputBuffer);
                         tempOutputBuffer = null;
                     }
 
-                    CryptographicOperations.ZeroMemory(new Span<byte>(tempInputBuffer, 0, numWholeBlocksInBytes));
-                    ArrayPool<byte>.Shared.Return(tempInputBuffer);
-                    tempInputBuffer = null;
+                    // For the input buffer we know how much was written, so clear that.
+                    // But still let it get reclaimed by the GC.
+                    if (tempInputBuffer != null)
+                    {
+                        CryptographicOperations.ZeroMemory(new Span<byte>(tempInputBuffer, 0, numWholeBlocksInBytes));
+                        tempInputBuffer = null;
+                    }
+
+                    throw;
                 }
             }
 
@@ -557,6 +569,8 @@ namespace System.Security.Cryptography
                     if (_transform.CanTransformMultipleBlocks && numWholeBlocks > 1)
                     {
                         int numWholeBlocksInBytes = numWholeBlocks * _inputBlockSize;
+                        
+                        // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
                         byte[] tempOutputBuffer = ArrayPool<byte>.Shared.Rent(numWholeBlocks * _outputBlockSize);
                         numOutputBytes = 0;
 
@@ -576,12 +590,15 @@ namespace System.Security.Cryptography
 
                             currentInputIndex += numWholeBlocksInBytes;
                             bytesToWrite -= numWholeBlocksInBytes;
+                            CryptographicOperations.ZeroMemory(new Span<byte>(tempOutputBuffer, 0, numOutputBytes));
+                            ArrayPool<byte>.Shared.Return(tempOutputBuffer);
+                            tempOutputBuffer = null;
                         }
-                        finally
+                        catch
                         {
                             CryptographicOperations.ZeroMemory(new Span<byte>(tempOutputBuffer, 0, numOutputBytes));
-                            ArrayPool<byte>.Shared.Return(tempOutputBuffer);
                             tempOutputBuffer = null;
+                            throw;
                         }
                     }
                     else
index f636645..c78b7e7 100644 (file)
@@ -97,24 +97,17 @@ namespace System.Security.Cryptography
             if (_disposed)
                 throw new ObjectDisposedException(null);
 
-            // Default the buffer size to 4K.
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
             byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
 
-            try
+            int bytesRead;
+            while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0)
             {
-                int bytesRead;
-                while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0)
-                {
-                    HashCore(buffer, 0, bytesRead);
-                }
-
-                return CaptureHashCodeAndReinitialize();
-            }
-            finally
-            {
-                CryptographicOperations.ZeroMemory(buffer);
-                ArrayPool<byte>.Shared.Return(buffer);
+                HashCore(buffer, 0, bytesRead);
             }
+
+            ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
+            return CaptureHashCodeAndReinitialize();
         }
 
         private byte[] CaptureHashCodeAndReinitialize()
@@ -219,17 +212,12 @@ namespace System.Security.Cryptography
 
         protected virtual void HashCore(ReadOnlySpan<byte> source)
         {
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
             byte[] array = ArrayPool<byte>.Shared.Rent(source.Length);
-            try
-            {
-                source.CopyTo(array);
-                HashCore(array, 0, source.Length);
-            }
-            finally
-            {
-                Array.Clear(array, 0, source.Length);
-                ArrayPool<byte>.Shared.Return(array);
-            }
+            source.CopyTo(array);
+            HashCore(array, 0, source.Length);
+            Array.Clear(array, 0, source.Length);
+            ArrayPool<byte>.Shared.Return(array);
         }
 
         protected virtual bool TryHashFinal(Span<byte> destination, out int bytesWritten)
index 3192bd6..e1c8262 100644 (file)
@@ -244,7 +244,8 @@ namespace Internal.Cryptography.Pal
             }
             finally
             {
-                ArrayPool<byte>.Shared.Return(crlDistributionPoints.Array);
+                // The data came from a certificate, so it's public.
+                CryptoPool.Return(crlDistributionPoints.Array, clearSize: 0);
             }
 
             return null;
index 8037861..314cc3e 100644 (file)
@@ -6,6 +6,7 @@ using System;
 using System.Buffers;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Runtime.InteropServices;
 using System.Security.Cryptography;
 using System.Security.Cryptography.Asn1;
 using System.Security.Cryptography.X509Certificates;
@@ -203,7 +204,7 @@ namespace Internal.Cryptography.Pal
                         ref _remainingDownloadTime);
 
                     // The AIA record is contained in a public structure, so no need to clear it.
-                    ArrayPool<byte>.Shared.Return(authorityInformationAccess.Array);
+                    CryptoPool.Return(authorityInformationAccess.Array, clearSize: 0);
 
                     if (downloaded == null)
                     {
@@ -229,7 +230,7 @@ namespace Internal.Cryptography.Pal
                 {
                     int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
                     Span<IntPtr> tempChain = stackalloc IntPtr[DefaultChainCapacity];
-                    IntPtr[] tempChainRent = null;
+                    byte[] tempChainRent = null;
 
                     if (chainSize <= tempChain.Length)
                     {
@@ -237,8 +238,9 @@ namespace Internal.Cryptography.Pal
                     }
                     else
                     {
-                        tempChainRent = ArrayPool<IntPtr>.Shared.Rent(chainSize);
-                        tempChain = tempChainRent.AsSpan(0, chainSize);
+                        int targetSize = checked(chainSize * IntPtr.Size);
+                        tempChainRent = CryptoPool.Rent(targetSize);
+                        tempChain = MemoryMarshal.Cast<byte, IntPtr>(tempChainRent.AsSpan(0, targetSize));
                     }
 
                     for (int i = 0; i < chainSize; i++)
@@ -276,10 +278,7 @@ namespace Internal.Cryptography.Pal
 
                     if (tempChainRent != null)
                     {
-                        // While the IntPtrs aren't secret, clearing them helps prevent
-                        // accidental use-after-free because of pooling.
-                        tempChain.Clear();
-                        ArrayPool<IntPtr>.Shared.Return(tempChainRent);
+                        CryptoPool.Return(tempChainRent);
                     }
                 }
             }
@@ -450,7 +449,7 @@ namespace Internal.Cryptography.Pal
                 string requestUrl = UrlPathAppend(baseUri, urlEncoded);
 
                 // Nothing sensitive is in the encoded request (it was sent via HTTP-non-S)
-                ArrayPool<byte>.Shared.Return(encoded.Array);
+                CryptoPool.Return(encoded.Array, clearSize: 0);
                 ArrayPool<char>.Shared.Return(urlEncoded.Array);
 
                 // https://tools.ietf.org/html/rfc6960#appendix-A describes both a GET and a POST
@@ -855,7 +854,7 @@ namespace Internal.Cryptography.Pal
             }
 
             string baseUrl = FindHttpAiaRecord(authorityInformationAccess, Oids.OcspEndpoint);
-            ArrayPool<byte>.Shared.Return(authorityInformationAccess.Array);
+            CryptoPool.Return(authorityInformationAccess.Array, clearSize: 0);
             return baseUrl;
         }
 
@@ -984,6 +983,7 @@ namespace Internal.Cryptography.Pal
                             if (_errors == null)
                             {
                                 int size = Math.Max(DefaultChainCapacity, errorDepth + 1);
+                                // Since ErrorCollection is a non-public type, this is a private pool.
                                 _errors = ArrayPool<ErrorCollection>.Shared.Rent(size);
 
                                 // We only do spares writes.
index 182cb68..03be022 100644 (file)
@@ -18,6 +18,9 @@
     <Compile Include="$(CommonPath)\System\Memory\PointerMemoryManager.cs">
       <Link>Common\System\Memory\PointerMemoryManager.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Security\Cryptography\CryptoPool.cs">
+      <Link>Common\System\Security\Cryptography\CryptoPool.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\System\Security\Cryptography\Oids.cs">
       <Link>Common\System\Security\Cryptography\Oids.cs</Link>
     </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs">
       <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.PooledCrypto.cs">
+      <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.PooledCrypto.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs">
       <Link>Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs</Link>
     </Compile>