From 254f31e756957e3f6d46439f6e3760ca267740f0 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 17 May 2019 06:28:17 -0700 Subject: [PATCH] Clean up usage of ArrayPool in the Cryptography assemblies 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 --- .../Interop.Crypto.cs | 30 --- .../Interop.Encode.cs | 9 +- .../Interop.PooledCrypto.cs | 40 ++++ .../Cryptography/Asn1Reader/AsnReader.BitString.cs | 5 +- .../Asn1Reader/AsnReader.GeneralizedTime.cs | 10 +- .../Cryptography/Asn1Reader/AsnReader.Integer.cs | 9 +- .../Asn1Reader/AsnReader.OctetString.cs | 7 +- .../Cryptography/Asn1Reader/AsnReader.Oid.cs | 5 +- .../Cryptography/Asn1Reader/AsnReader.Text.cs | 6 +- .../Cryptography/Asn1Reader/AsnReader.UtcTime.cs | 3 +- .../Cryptography/Asn1Reader/AsnWriter.BitString.cs | 227 +++++++++++++++++++-- .../Cryptography/Asn1Reader/AsnWriter.Oid.cs | 6 +- .../Cryptography/Asn1Reader/AsnWriter.Text.cs | 8 +- .../Security/Cryptography/Asn1Reader/AsnWriter.cs | 44 ++-- .../src/System/Security/Cryptography/CngPkcs8.cs | 24 +-- .../src/System/Security/Cryptography/CryptoPool.cs | 33 +++ .../Security/Cryptography/DSACng.SignVerify.cs | 95 ++++----- .../src/System/Security/Cryptography/DSAOpenSsl.cs | 10 +- .../Cryptography/ECDiffieHellmanOpenSsl.Derive.cs | 5 +- .../System/Security/Cryptography/ECDsaOpenSsl.cs | 5 +- .../Security/Cryptography/EccKeyFormatHelper.cs | 62 ++---- .../Security/Cryptography/KeyFormatHelper.cs | 16 +- .../Cryptography/PasswordBasedEncryption.cs | 44 ++-- .../src/System/Security/Cryptography/Pkcs12Kdf.cs | 5 +- .../Security/Cryptography/RSACng.EncryptDecrypt.cs | 8 +- .../src/System/Security/Cryptography/RSAOpenSsl.cs | 22 +- .../Security/Cryptography/RSASecurityTransforms.cs | 31 ++- .../Security/Cryptography/RsaPaddingProcessor.cs | 24 +-- .../src/Internal/Cryptography/OpenSslCipher.cs | 8 +- .../System.Security.Cryptography.Algorithms.csproj | 5 +- .../src/System/Security/Cryptography/DSA.cs | 31 ++- .../src/System/Security/Cryptography/ECDsa.cs | 30 ++- .../src/System/Security/Cryptography/RSA.cs | 16 +- .../Security/Cryptography/RandomNumberGenerator.cs | 2 + .../Security/Cryptography/Rfc2898DeriveBytes.cs | 79 +++---- .../tests/Rfc2898Tests.cs | 50 ++++- .../src/System.Security.Cryptography.Cng.csproj | 3 + .../System.Security.Cryptography.Encoding.csproj | 3 + ...tem.Security.Cryptography.Encoding.Tests.csproj | 5 +- .../System.Security.Cryptography.OpenSsl.csproj | 3 + .../Cryptography/Pal/AnyOS/ManagedPal.Asn.cs | 6 +- .../Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs | 14 +- .../Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs | 6 +- .../Cryptography/Pal/Windows/HelpersWindows.cs | 10 +- .../src/System.Security.Cryptography.Pkcs.csproj | 3 + .../Security/Cryptography/Pkcs/CmsSignature.DSA.cs | 12 +- .../Cryptography/Pkcs/CmsSignature.ECDsa.cs | 13 +- .../Security/Cryptography/Pkcs/Pkcs12Builder.cs | 10 +- .../Cryptography/Pkcs/Pkcs12SafeContents.cs | 4 +- .../Cryptography/Pkcs/Pkcs8PrivateKeyInfo.cs | 4 +- .../System/Security/Cryptography/Pkcs/SignedCms.cs | 5 +- .../System.Security.Cryptography.Primitives.csproj | 6 +- .../Security/Cryptography/AsymmetricAlgorithm.cs | 32 +-- .../System/Security/Cryptography/CryptoStream.cs | 33 ++- .../System/Security/Cryptography/HashAlgorithm.cs | 36 ++-- .../src/Internal/Cryptography/Pal.Unix/CrlCache.cs | 3 +- .../Pal.Unix/OpenSslX509ChainProcessor.cs | 20 +- ...m.Security.Cryptography.X509Certificates.csproj | 6 + 58 files changed, 752 insertions(+), 499 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.PooledCrypto.cs create mode 100644 src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs index 8671e2e..6190ec0 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs @@ -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 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 RentDynamicBuffer(NegativeSizeReadMethod method, THandle handle) - { - int negativeSize = method(handle, null, 0); - - if (negativeSize > 0) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - int targetSize = -negativeSize; - byte[] bytes = ArrayPool.Shared.Rent(targetSize); - - int ret = method(handle, bytes, targetSize); - - if (ret != 1) - { - ArrayPool.Shared.Return(bytes); - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } - - return new ArraySegment(bytes, 0, targetSize); - } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs index 309ab2a..433bbfa 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs @@ -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.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.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 index 0000000..ddcba2d --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.PooledCrypto.cs @@ -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 RentAsn1StringBytes(IntPtr asn1) + { + return RentDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1); + } + + private static ArraySegment RentDynamicBuffer(NegativeSizeReadMethod 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(bytes, 0, targetSize); + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.BitString.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.BitString.cs index fb12032..2d03931 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.BitString.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.BitString.cs @@ -351,7 +351,7 @@ namespace System.Security.Cryptography.Asn1 memory = PeekEncodedValue(); // Guaranteed long enough - byte[] rented = ArrayPool.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.Shared.Return(rented); + CryptoPool.Return(rented, dataLength); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.GeneralizedTime.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.GeneralizedTime.cs index 59b0eae..e1ad8c1 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.GeneralizedTime.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.GeneralizedTime.cs @@ -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 tmpSpace = stackalloc byte[64]; + ReadOnlySpan 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.Shared.Return(rented); + CryptoPool.Return(rented, contents.Length); } _data = _data.Slice(bytesRead); diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Integer.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Integer.cs index 22b3a6c..96275d0 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Integer.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Integer.cs @@ -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.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.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); diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.OctetString.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.OctetString.cs index fb2a983..5394dbe 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.OctetString.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.OctetString.cs @@ -230,7 +230,7 @@ namespace System.Security.Cryptography.Asn1 memory = PeekEncodedValue(); // Guaranteed long enough - byte[] rented = ArrayPool.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.Shared.Return(rented); + CryptoPool.Return(rented, dataLength); } } @@ -571,7 +570,7 @@ namespace System.Security.Cryptography.Asn1 if (tmpSpace.Length < octetStringLength) { - rented = ArrayPool.Shared.Rent(octetStringLength); + rented = CryptoPool.Rent(octetStringLength); tmpSpace = rented; } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Oid.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Oid.cs index 8a5f210..e767cf5 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Oid.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Oid.cs @@ -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.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.Shared.Return(tmpBytes); + CryptoPool.Return(tmpBytes, bytesWritten); } private string ReadObjectIdentifierAsString(Asn1Tag expectedTag, out int totalBytesRead) diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Text.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Text.cs index b51b799..5c1dd29 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Text.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.Text.cs @@ -656,8 +656,7 @@ namespace System.Security.Cryptography.Asn1 { if (rented != null) { - Array.Clear(rented, 0, contents.Length); - ArrayPool.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.Shared.Return(rented); + CryptoPool.Return(rented, contents.Length); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.UtcTime.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.UtcTime.cs index 24ff44a..24c0b14 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.UtcTime.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnReader.UtcTime.cs @@ -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.Shared.Return(rented); + CryptoPool.Return(rented, contents.Length); } _data = _data.Slice(bytesRead); diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.BitString.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.BitString.cs index 89ae861..a5a0675 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.BitString.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.BitString.cs @@ -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 payload, int unusedBitCount) +#if netcoreapp || uap || NETCOREAPP + /// + /// Write a Bit String value via a callback, with a tag UNIVERSAL 3. + /// + /// The total number of bytes to write. + /// A state object to pass to . + /// A callback to invoke for populating the Bit String. + /// + /// The number of trailing bits which are not semantic. + /// + /// + /// is negative --OR-- + /// is not in the range [0,7] + /// + /// + /// is 0 and is not 0 --OR-- + /// is not 0 and any of the bits identified by + /// is set + /// + /// The writer has been Disposed. + public void WriteBitString( + int byteLength, + TState state, + SpanAction action, + int unusedBitCount = 0) + { + WriteBitStringCore(Asn1Tag.PrimitiveBitString, byteLength, state, action, unusedBitCount); + } + + /// + /// Write a Bit String value via a callback, with a specified tag. + /// + /// The tag to write. + /// The total number of bytes to write. + /// A state object to pass to . + /// A callback to invoke for populating the Bit String. + /// + /// The number of trailing bits which are not semantic. + /// + /// + /// . is + /// , but + /// . is not correct for + /// the method + /// + /// + /// is negative --OR-- + /// is not in the range [0,7] + /// + /// + /// is 0 and is not 0 --OR-- + /// is not 0 and any of the bits identified by + /// is set + /// + /// The writer has been Disposed. + public void WriteBitString( + Asn1Tag tag, + int byteLength, + TState state, + SpanAction 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( + Asn1Tag tag, + int byteLength, + TState state, + SpanAction action, + int unusedBitCount = 0) + { + if (byteLength == 0) + { + WriteBitStringCore(tag, ReadOnlySpan.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 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 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 remainingData = payload; Span dest; diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Oid.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Oid.cs index 47c8d52..f8c64e1 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Oid.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Oid.cs @@ -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.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); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Text.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Text.cs index 348c24c..83cb6e8 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Text.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.Text.cs @@ -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.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); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.cs index 2b4c282..ad0ce19 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnWriter.cs @@ -57,7 +57,8 @@ namespace System.Security.Cryptography.Asn1 { Array.Clear(_buffer, 0, _offset); #if !CHECK_ACCURATE_ENSURE - ArrayPool.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(_buffer, 0, _offset); } + /// + /// Determines if would produce an output identical to + /// . + /// + /// + /// if the pending encoded data is identical to , + /// otherwise. + /// + /// + /// A or has not been closed via + /// or . + /// + /// The writer has been Disposed. + public bool ValueEquals(ReadOnlySpan 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.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 localPool = ArrayPool.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 span) diff --git a/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs b/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs index 652de7d..6316db7 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CngPkcs8.cs @@ -151,7 +151,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(decryptedSpan); - ArrayPool.Shared.Return(decrypted.Array); + CryptoPool.Return(decrypted.Array); } } } @@ -203,7 +203,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(decryptedSpan); - ArrayPool.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.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.Shared.Return(rented); - rented = ArrayPool.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.Shared.Return(rented); + CryptoPool.Return(rented, rentWritten); } } @@ -275,7 +275,7 @@ namespace System.Security.Cryptography { Debug.Assert(pbeParameters != null); - byte[] rented = ArrayPool.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.Shared.Return(rented); - rented = ArrayPool.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.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 index 0000000..9d6413e --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs @@ -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.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.Shared.Return(array, clearWholeArray); + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs b/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs index 2b2452d..9fb82a9 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs @@ -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 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 stackBuf = stackalloc byte[WindowsMaxQSize]; + ReadOnlySpan source = AdjustHashSizeIfNecessary(rgbHash, stackBuf); + + using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle()) { - if (arrayToReturnToArrayPool != null) + unsafe { - Array.Clear(arrayToReturnToArrayPool, 0, source.Length); - ArrayPool.Shared.Return(arrayToReturnToArrayPool); + return CngCommon.SignHash(keyHandle, source, AsymmetricPaddingMode.None, null, source.Length * 2); } } } public override unsafe bool TryCreateSignature(ReadOnlySpan hash, Span destination, out int bytesWritten) { - byte[] arrayToReturnToArrayPool = AdjustHashSizeIfNecessaryWithArrayPool(ref hash); - try + Span stackBuf = stackalloc byte[WindowsMaxQSize]; + ReadOnlySpan 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.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 hash, ReadOnlySpan signature) { - byte[] arrayToReturnToArrayPool = AdjustHashSizeIfNecessaryWithArrayPool(ref hash); - try - { - using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle()) - { - unsafe - { - return CngCommon.VerifyHash(keyHandle, hash, signature, AsymmetricPaddingMode.None, null); - } - } - } - finally + Span stackBuf = stackalloc byte[WindowsMaxQSize]; + ReadOnlySpan source = AdjustHashSizeIfNecessary(hash, stackBuf); + + using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle()) { - if (arrayToReturnToArrayPool != null) + unsafe { - Array.Clear(arrayToReturnToArrayPool, 0, hash.Length); - ArrayPool.Shared.Return(arrayToReturnToArrayPool); + return CngCommon.VerifyHash(keyHandle, source, signature, AsymmetricPaddingMode.None, null); } } } - private byte[] AdjustHashSizeIfNecessaryWithArrayPool(ref ReadOnlySpan hash) + private ReadOnlySpan AdjustHashSizeIfNecessary(ReadOnlySpan hash, Span 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.Shared.Rent(qLength); - hash.CopyTo(new Span(arrayPoolPaddedHash, qLength - hash.Length, hash.Length)); - hash = new ReadOnlySpan(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() diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs index e075975..f4be1e7 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs @@ -190,7 +190,7 @@ namespace System.Security.Cryptography SafeDsaHandle key = _key.Value; int signatureSize = Interop.Crypto.DsaEncodedSignatureSize(key); - byte[] signature = ArrayPool.Shared.Rent(signatureSize); + byte[] signature = CryptoPool.Rent(signatureSize); try { bool success = Interop.Crypto.DsaSign(key, rgbHash, new Span(signature, 0, signatureSize), out signatureSize); @@ -211,8 +211,7 @@ namespace System.Security.Cryptography } finally { - Array.Clear(signature, 0, signatureSize); - ArrayPool.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.Shared.Rent(signatureSize); + byte[] signature = CryptoPool.Rent(signatureSize); try { bool success = Interop.Crypto.DsaSign(key, hash, new Span(signature, 0, signatureSize), out signatureSize); @@ -242,8 +241,7 @@ namespace System.Security.Cryptography } finally { - Array.Clear(signature, 0, signatureSize); - ArrayPool.Shared.Return(signature); + CryptoPool.Return(signature, signatureSize); } if (converted.Length <= destination.Length) diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs index 6426aef..8de7899 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs @@ -157,7 +157,7 @@ namespace System.Security.Cryptography if (secretLength > StackAllocMax) { - rented = ArrayPool.Shared.Rent(secretLength); + rented = CryptoPool.Rent(secretLength); secret = new Span(rented, 0, secretLength); } else @@ -190,8 +190,7 @@ namespace System.Security.Cryptography if (rented != null) { - Array.Clear(rented, 0, secretLength); - ArrayPool.Shared.Return(rented); + CryptoPool.Return(rented, secretLength); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs index 292fd65..4f5d488 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -95,7 +95,7 @@ namespace System.Security.Cryptography byte[] converted; int signatureLength = Interop.Crypto.EcDsaSize(key); - byte[] signature = ArrayPool.Shared.Rent(signatureLength); + byte[] signature = CryptoPool.Rent(signatureLength); try { if (!Interop.Crypto.EcDsaSign(hash, new Span(signature, 0, signatureLength), ref signatureLength, key)) @@ -107,8 +107,7 @@ namespace System.Security.Cryptography } finally { - Array.Clear(signature, 0, signatureLength); - ArrayPool.Shared.Return(signature); + CryptoPool.Return(signature, signatureLength); } if (converted.Length <= destination.Length) diff --git a/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs index aca89ee..ca712fc 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/EccKeyFormatHelper.cs @@ -213,29 +213,19 @@ namespace System.Security.Cryptography if (keyParameters != null && algId.Parameters != null) { ReadOnlySpan algIdParameters = algId.Parameters.Value.Span; - byte[] verify = ArrayPool.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(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.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 publicKeyBytes = stackalloc byte[0]; - byte[] publicKeyRented = null; - - if (publicKeyLength < 256) - { - publicKeyBytes = stackalloc byte[publicKeyLength]; - } - else - { - publicKeyRented = ArrayPool.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.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) diff --git a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs index ff8a28e..83a9203 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.cs @@ -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.Shared.Rent(epki.EncryptedData.Length); + byte[] decrypted = CryptoPool.Rent(epki.EncryptedData.Length); Memory decryptedMemory = decrypted; try @@ -246,7 +246,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(decryptedMemory.Span); - ArrayPool.Shared.Return(decrypted); + CryptoPool.Return(decrypted, clearSize: 0); } } @@ -340,7 +340,7 @@ namespace System.Security.Cryptography Span salt = stackalloc byte[16]; // We need at least one block size beyond the input data size. - encryptedRent = ArrayPool.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.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.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.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.Shared.Return(decrypted.Array); + CryptoPool.Return(decrypted.Array, clearSize: 0); } } @@ -524,7 +524,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(decrypted); - ArrayPool.Shared.Return(decrypted.Array); + CryptoPool.Return(decrypted.Array, clearSize: 0); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs b/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs index 2242bdb..44c4e81 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs @@ -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 ArrayPool => ArrayPool.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); } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs b/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs index fcb7d64..f7f7687 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs @@ -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.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.Shared.Return(IRented); + CryptoPool.Return(IRented, clearSize: 0); } hash.Dispose(); diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSACng.EncryptDecrypt.cs b/src/libraries/Common/src/System/Security/Cryptography/RSACng.EncryptDecrypt.cs index 75444d4..9874132 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSACng.EncryptDecrypt.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSACng.EncryptDecrypt.cs @@ -69,7 +69,7 @@ namespace System.Security.Cryptography { if (encrypt && data.Length == 0) { - byte[] rented = ArrayPool.Shared.Rent(modulusSizeInBytes); + byte[] rented = CryptoPool.Rent(modulusSizeInBytes); Span paddedMessage = new Span(rented, 0, modulusSizeInBytes); try @@ -95,7 +95,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(paddedMessage); - ArrayPool.Shared.Return(rented); + CryptoPool.Return(rented, clearSize: 0); } } @@ -157,7 +157,7 @@ namespace System.Security.Cryptography { if (encrypt && data.Length == 0) { - byte[] rented = ArrayPool.Shared.Rent(modulusSizeInBytes); + byte[] rented = CryptoPool.Rent(modulusSizeInBytes); Span paddedMessage = new Span(rented, 0, modulusSizeInBytes); try @@ -183,7 +183,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(paddedMessage); - ArrayPool.Shared.Return(rented); + CryptoPool.Return(rented, clearSize: 0); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs index e2617db..88092e2 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSAOpenSsl.cs @@ -97,7 +97,7 @@ namespace System.Security.Cryptography try { - buf = ArrayPool.Shared.Rent(rsaSize); + buf = CryptoPool.Rent(rsaSize); destination = new Span(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.Shared.Return(buf); + CryptoPool.Return(buf, clearSize: 0); } } @@ -216,7 +216,7 @@ namespace System.Security.Cryptography if (rsaPaddingProcessor != null) { - paddingBuf = ArrayPool.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.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.Shared.Rent(rsaSize); + byte[] rented = CryptoPool.Rent(rsaSize); Span tmp = new Span(rented, 0, rsaSize); try @@ -329,7 +329,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(tmp); - ArrayPool.Shared.Return(rented); + CryptoPool.Return(rented, clearSize: 0); } } else @@ -712,15 +712,14 @@ namespace System.Security.Cryptography return false; } - byte[] pssRented = ArrayPool.Shared.Rent(bytesRequired); + byte[] pssRented = CryptoPool.Rent(bytesRequired); Span pssBytes = new Span(pssRented, 0, bytesRequired); processor.EncodePss(hash, pssBytes, KeySize); int ret = Interop.Crypto.RsaSignPrimitive(pssBytes, destination, rsa); - pssBytes.Clear(); - ArrayPool.Shared.Return(pssRented); + CryptoPool.Return(pssRented, bytesRequired); CheckReturn(ret); @@ -787,7 +786,7 @@ namespace System.Security.Cryptography return false; } - byte[] rented = ArrayPool.Shared.Rent(requiredBytes); + byte[] rented = CryptoPool.Rent(requiredBytes); Span unwrapped = new Span(rented, 0, requiredBytes); try @@ -804,8 +803,7 @@ namespace System.Security.Cryptography } finally { - unwrapped.Clear(); - ArrayPool.Shared.Return(rented); + CryptoPool.Return(rented, requiredBytes); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs index b6a0518..f93f015 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSASecurityTransforms.cs @@ -310,7 +310,7 @@ namespace System.Security.Cryptography throw new CryptographicException(SR.Cryptography_InvalidPaddingMode); } - byte[] rented = ArrayPool.Shared.Rent(rsaSize); + byte[] rented = CryptoPool.Rent(rsaSize); Span tmp = new Span(rented, 0, rsaSize); try @@ -333,8 +333,8 @@ namespace System.Security.Cryptography } finally { - tmp.Clear(); - ArrayPool.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.Shared.Rent(maxOutputSize); - Span contentsSpan = Span.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(rented, 0, bytesWritten); + Span contentsSpan = new Span(rented, 0, bytesWritten); return contentsSpan.ToArray(); } finally { - CryptographicOperations.ZeroMemory(contentsSpan); - ArrayPool.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.Shared.Rent(modulusSizeInBytes); + byte[] rented = CryptoPool.Rent(modulusSizeInBytes); Span unpaddedData = Span.Empty; try @@ -456,7 +455,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(unpaddedData); - ArrayPool.Shared.Return(rented); + CryptoPool.Return(rented, clearSize: 0); } } @@ -585,7 +584,7 @@ namespace System.Security.Cryptography return false; } - byte[] rented = ArrayPool.Shared.Rent(rsaSize); + byte[] rented = CryptoPool.Rent(rsaSize); Span buf = new Span(rented, 0, rsaSize); processor.EncodePss(hash, buf, keySize); @@ -596,7 +595,7 @@ namespace System.Security.Cryptography finally { CryptographicOperations.ZeroMemory(buf); - ArrayPool.Shared.Return(rented); + CryptoPool.Return(rented, clearSize: 0); } } @@ -653,7 +652,7 @@ namespace System.Security.Cryptography return false; } - byte[] rented = ArrayPool.Shared.Rent(rsaSize); + byte[] rented = CryptoPool.Rent(rsaSize); Span unwrapped = new Span(rented, 0, rsaSize); try @@ -673,8 +672,8 @@ namespace System.Security.Cryptography } finally { - unwrapped.Clear(); - ArrayPool.Shared.Return(rented); + CryptographicOperations.ZeroMemory(unwrapped); + CryptoPool.Return(rented, clearSize: 0); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs index 34de84e..2f54f03 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RsaPaddingProcessor.cs @@ -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.Shared.Rent(db.Length); + dbMask = CryptoPool.Rent(db.Length); dbMaskSpan = new Span(dbMask, 0, db.Length); Mgf1(hasher, seed, dbMaskSpan); @@ -166,8 +165,8 @@ namespace System.Security.Cryptography { if (dbMask != null) { - dbMaskSpan.Clear(); - ArrayPool.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.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.Shared.Return(tmp); + CryptoPool.Return(tmp, source.Length); } } } @@ -303,7 +301,7 @@ namespace System.Security.Cryptography Span hDest = em.Slice(dbLen, _hLen); em[emLen - 1] = 0xBC; - byte[] dbMaskRented = ArrayPool.Shared.Rent(dbLen); + byte[] dbMaskRented = CryptoPool.Rent(dbLen); Span dbMask = new Span(dbMaskRented, 0, dbLen); using (IncrementalHash hasher = IncrementalHash.CreateHash(_hashAlgorithmName)) @@ -348,8 +346,8 @@ namespace System.Security.Cryptography } } - dbMask.Clear(); - ArrayPool.Shared.Return(dbMaskRented); + CryptographicOperations.ZeroMemory(dbMask); + CryptoPool.Return(dbMaskRented, clearSize: 0); } internal bool VerifyPss(ReadOnlySpan mHash, ReadOnlySpan em, int keySize) @@ -397,7 +395,7 @@ namespace System.Security.Cryptography } // 7. dbMask = MGF(H, emLen - hLen - 1) - byte[] dbMaskRented = ArrayPool.Shared.Rent(maskedDb.Length); + byte[] dbMaskRented = CryptoPool.Rent(maskedDb.Length); Span dbMask = new Span(dbMaskRented, 0, maskedDb.Length); try @@ -457,8 +455,8 @@ namespace System.Security.Cryptography } finally { - dbMask.Clear(); - ArrayPool.Shared.Return(dbMaskRented); + CryptographicOperations.ZeroMemory(dbMask); + CryptoPool.Return(dbMaskRented, clearSize: 0); } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/OpenSslCipher.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/OpenSslCipher.cs index 470e616..f625574 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/OpenSslCipher.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/OpenSslCipher.cs @@ -54,18 +54,18 @@ namespace Internal.Cryptography // OpenSSL 1.1 does not allow partial overlap. if (input == output && inputOffset != outputOffset) { - byte[] tmp = ArrayPool.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.Shared.Return(tmp); + CryptoPool.Return(tmp, written); } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index 230884a..fa63b6e 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -105,6 +105,9 @@ Common\System\Memory\PointerMemoryManager.cs + + Common\System\Security\Cryptography\CryptoPool.cs + Common\System\Security\Cryptography\DSAKeyFormatHelper.cs @@ -727,4 +730,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs index b620c8a..4f39f36 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs @@ -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 data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) { + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. byte[] array = ArrayPool.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 buf = stackalloc byte[128]; + ReadOnlySpan hash = stackalloc byte[0]; + + if (TryHashData(data, buf, hashAlgorithm, out int hashLength)) + { + hash = buf.Slice(0, hashLength); + } + else { - int hashLength = 0; - byte[] hash = ArrayPool.Shared.Rent(i); + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. + byte[] array = ArrayPool.Shared.Rent(data.Length); try { - if (TryHashData(data, hash, hashAlgorithm, out hashLength)) - { - return VerifySignature(new ReadOnlySpan(hash, 0, hashLength), signature); - } + data.CopyTo(array); + hash = HashData(array, 0, data.Length, hashAlgorithm); } finally { - Array.Clear(hash, 0, hashLength); - ArrayPool.Shared.Return(hash); + Array.Clear(array, 0, data.Length); + ArrayPool.Shared.Return(array); } } + + return VerifySignature(hash, signature); } public virtual bool VerifySignature(ReadOnlySpan hash, ReadOnlySpan signature) => diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs index fa8e0e2..2e1fae7 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs @@ -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 buf = stackalloc byte[128]; + ReadOnlySpan hash = stackalloc byte[0]; + + if (TryHashData(data, buf, hashAlgorithm, out int hashLength)) { - int hashLength = 0; - byte[] hash = ArrayPool.Shared.Rent(i); + hash = buf.Slice(0, hashLength); + } + else + { + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. + byte[] array = ArrayPool.Shared.Rent(data.Length); try { - if (TryHashData(data, hash, hashAlgorithm, out hashLength)) - { - return VerifyHash(new ReadOnlySpan(hash, 0, hashLength), signature); - } + data.CopyTo(array); + hash = HashData(array, 0, data.Length, hashAlgorithm); } finally { - Array.Clear(hash, 0, hashLength); - ArrayPool.Shared.Return(hash); + Array.Clear(array, 0, data.Length); + ArrayPool.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 data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) { + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. byte[] array = ArrayPool.Shared.Rent(data.Length); try { diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs index f2bbc55..3be56f9 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs @@ -92,6 +92,7 @@ namespace System.Security.Cryptography protected virtual bool TryHashData(ReadOnlySpan data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) { byte[] result; + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. byte[] array = ArrayPool.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.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.Shared.Return(hash); + CryptoPool.Return(hash, hashLength); } } } @@ -329,7 +329,7 @@ namespace System.Security.Cryptography while (true) { - byte[] rented = ArrayPool.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.Shared.Return(rented); + CryptoPool.Return(rented, pkcs1Size); } } } @@ -377,7 +376,7 @@ namespace System.Security.Cryptography while (true) { - byte[] rented = ArrayPool.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.Shared.Return(rented); + CryptoPool.Return(rented, pkcs1Size); } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs index e28f0dd..1986d56 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs @@ -54,6 +54,7 @@ namespace System.Security.Cryptography public virtual void GetBytes(Span data) { + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. byte[] array = ArrayPool.Shared.Rent(data.Length); try { @@ -76,6 +77,7 @@ namespace System.Security.Cryptography public virtual void GetNonZeroBytes(Span data) { + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. byte[] array = ArrayPool.Shared.Rent(data.Length); try { diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.cs index 2eecd85..e72eb83 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.cs @@ -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.Shared.Rent(_blockSize); - try - { - Span uiSpan = new Span(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 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.Shared.Return(ui); + for (int j = _buffer.Length - 1; j >= 0; j--) + { + _buffer[j] ^= uiSpan[j]; + } } + + // increment the block count. + _block++; } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2898Tests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2898Tests.cs index 1b240bf..224929c 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2898Tests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2898Tests.cs @@ -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() { diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj index 5d151bb..581bd6b 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj @@ -231,6 +231,9 @@ Common\System\Memory\PointerMemoryManager.cs + + Common\System\Security\Cryptography\CryptoPool.cs + Common\System\Security\Cryptography\CngPkcs8.cs diff --git a/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj b/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj index 882a8c5..8b6ffd9 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj +++ b/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj @@ -98,6 +98,9 @@ Common\System\Memory\PointerMemoryManager.cs + + Common\System\Security\Cryptography\CryptoPool.cs + Common\System\Security\Cryptography\Asn1\DirectoryStringAsn.xml diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj b/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj index aa90a02..ba32fda5 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj @@ -6,6 +6,9 @@ + + Common\System\Security\Cryptography\CryptoPool.cs + @@ -58,4 +61,4 @@ Common\System\Memory\PointerMemoryManager.cs - \ No newline at end of file + diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj b/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj index c28cda4..fc429bf 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj +++ b/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj @@ -105,6 +105,9 @@ Common\System\Memory\PointerMemoryManager.cs + + Common\System\Security\Cryptography\CryptoPool.cs + Common\System\Security\Cryptography\DSAOpenSsl.cs diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Asn.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Asn.cs index 47b2f91..489373a 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Asn.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Asn.cs @@ -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.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.Shared.Return(poolBytes); + CryptoPool.Return(poolBytes, data.Length); } } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs index 0a80301..d840024 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs @@ -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.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.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.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.Shared.Return(encryptedContentArray); + CryptoPool.Return(encryptedContentArray, encryptedContentLength); encryptedContentArray = null; } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs index 59625e4..a1e5968 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs @@ -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.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.Shared.Return(cek); + CryptoPool.Return(cek, cekLength); } } #else diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/HelpersWindows.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/HelpersWindows.cs index 1133625..252ee48 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/HelpersWindows.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/HelpersWindows.cs @@ -420,8 +420,7 @@ namespace Internal.Cryptography.Pal.Windows if (rented != null) { - Array.Clear(rented, 0, maxClear); - ArrayPool.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 pool = ArrayPool.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; } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index d34768d..d045a33 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -405,6 +405,9 @@ Common\System\Memory\PointerMemoryManager.cs + + Common\System\Security\Cryptography\CryptoPool.cs + Common\System\Security\Cryptography\Oids.cs diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs index 1ed7a6f..62cf6bd 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.DSA.cs @@ -69,8 +69,7 @@ namespace System.Security.Cryptography.Pkcs int bufSize = 2 * dsaParameters.Q.Length; #if netcoreapp - ArrayPool pool = ArrayPool.Shared; - byte[] rented = pool.Rent(bufSize); + byte[] rented = CryptoPool.Rent(bufSize); Span ieee = new Span(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 pool = ArrayPool.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; diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs index fdce23c..827e101 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.ECDsa.cs @@ -74,8 +74,7 @@ namespace System.Security.Cryptography.Pkcs } #if netcoreapp - ArrayPool pool = ArrayPool.Shared; - byte[] rented = pool.Rent(bufSize); + byte[] rented = CryptoPool.Rent(bufSize); Span ieee = new Span(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 pool = ArrayPool.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 diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs index a5f5739..7926dff 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12Builder.cs @@ -184,13 +184,13 @@ namespace System.Security.Cryptography.Pkcs ReadOnlySpan encodedSpan = contentsWriter.EncodeAsSpan(); - rentedAuthSafe = ArrayPool.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.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.Shared.Return(rentedMac); + // Already cleared + CryptoPool.Return(rentedMac, clearSize: 0); } if (rentedAuthSafe != null) { - ArrayPool.Shared.Return(rentedAuthSafe); + // Already cleared + CryptoPool.Return(rentedAuthSafe, clearSize: 0); } } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs index f094642..895dd66 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs @@ -366,7 +366,7 @@ namespace System.Security.Cryptography.Pkcs out bool isPkcs12); int cipherBlockBytes = cipher.BlockSize / 8; - byte[] encryptedRent = ArrayPool.Shared.Rent(contentsSpan.Length + cipherBlockBytes); + byte[] encryptedRent = CryptoPool.Rent(contentsSpan.Length + cipherBlockBytes); Span encryptedSpan = Span.Empty; Span iv = stackalloc byte[cipherBlockBytes]; Span salt = stackalloc byte[16]; @@ -424,7 +424,7 @@ namespace System.Security.Cryptography.Pkcs finally { CryptographicOperations.ZeroMemory(encryptedSpan); - ArrayPool.Shared.Return(encryptedRent); + CryptoPool.Return(encryptedRent, clearSize: 0); writer?.Dispose(); } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs8PrivateKeyInfo.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs8PrivateKeyInfo.cs index 5587f72..7b714429 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs8PrivateKeyInfo.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Pkcs8PrivateKeyInfo.cs @@ -197,7 +197,7 @@ namespace System.Security.Cryptography.Pkcs finally { CryptographicOperations.ZeroMemory(decryptedMemory.Span); - ArrayPool.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.Shared.Return(decrypted.Array); + CryptoPool.Return(decrypted.Array, clearSize: 0); } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs index 330bbf4..1e80dd0 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/SignedCms.cs @@ -243,7 +243,7 @@ namespace System.Security.Cryptography.Pkcs return inner; } - rented = ArrayPool.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.Shared.Return(rented); + CryptoPool.Return(rented, bytesWritten); } } diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System.Security.Cryptography.Primitives.csproj b/src/libraries/System.Security.Cryptography.Primitives/src/System.Security.Cryptography.Primitives.csproj index 9a76ba0..834a575 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/System.Security.Cryptography.Primitives.csproj +++ b/src/libraries/System.Security.Cryptography.Primitives/src/System.Security.Cryptography.Primitives.csproj @@ -30,6 +30,9 @@ Internal\Cryptography\Helpers.cs + + Common\System\Security\Cryptography\CryptoPool.cs + Common\System\Security\Cryptography\KeySizeHelpers.cs @@ -40,9 +43,10 @@ + - \ No newline at end of file + diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/AsymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/AsymmetricAlgorithm.cs index 83c158f..359bd68 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/AsymmetricAlgorithm.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/AsymmetricAlgorithm.cs @@ -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 writtenSpan = Span.Empty; - byte[] buf = ArrayPool.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(buf, 0, bytesWritten); + Span writtenSpan = new Span(buf, 0, bytesWritten); return writtenSpan.ToArray(); } } finally { - if (writtenSpan.Length > 0) - { - CryptographicOperations.ZeroMemory(writtenSpan); - } - - ArrayPool.Shared.Return(buf); + CryptoPool.Return(buf, bytesWritten); } bufSize = checked(bufSize * 2); @@ -215,28 +208,23 @@ namespace System.Security.Cryptography while (true) { - Span writtenSpan = Span.Empty; - byte[] buf = ArrayPool.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(buf, 0, bytesWritten); + Span writtenSpan = new Span(buf, 0, bytesWritten); return writtenSpan.ToArray(); } } finally { - if (writtenSpan.Length > 0) - { - CryptographicOperations.ZeroMemory(writtenSpan); - } - - ArrayPool.Shared.Return(buf); + CryptoPool.Return(buf, bytesWritten); } bufSize = checked(bufSize * 2); diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs index efa6cfd..bdb6ec6 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/CryptoStream.cs @@ -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.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.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(tempInputBuffer, 0, numWholeBlocksInBytes)); + ArrayPool.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.Shared.Return(tempOutputBuffer); tempOutputBuffer = null; } - CryptographicOperations.ZeroMemory(new Span(tempInputBuffer, 0, numWholeBlocksInBytes)); - ArrayPool.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(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.Shared.Rent(numWholeBlocks * _outputBlockSize); numOutputBytes = 0; @@ -576,12 +590,15 @@ namespace System.Security.Cryptography currentInputIndex += numWholeBlocksInBytes; bytesToWrite -= numWholeBlocksInBytes; + CryptographicOperations.ZeroMemory(new Span(tempOutputBuffer, 0, numOutputBytes)); + ArrayPool.Shared.Return(tempOutputBuffer); + tempOutputBuffer = null; } - finally + catch { CryptographicOperations.ZeroMemory(new Span(tempOutputBuffer, 0, numOutputBytes)); - ArrayPool.Shared.Return(tempOutputBuffer); tempOutputBuffer = null; + throw; } } else diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/HashAlgorithm.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/HashAlgorithm.cs index f636645..c78b7e7 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/HashAlgorithm.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/HashAlgorithm.cs @@ -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.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.Shared.Return(buffer); + HashCore(buffer, 0, bytesRead); } + + ArrayPool.Shared.Return(buffer, clearArray: true); + return CaptureHashCodeAndReinitialize(); } private byte[] CaptureHashCodeAndReinitialize() @@ -219,17 +212,12 @@ namespace System.Security.Cryptography protected virtual void HashCore(ReadOnlySpan source) { + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. byte[] array = ArrayPool.Shared.Rent(source.Length); - try - { - source.CopyTo(array); - HashCore(array, 0, source.Length); - } - finally - { - Array.Clear(array, 0, source.Length); - ArrayPool.Shared.Return(array); - } + source.CopyTo(array); + HashCore(array, 0, source.Length); + Array.Clear(array, 0, source.Length); + ArrayPool.Shared.Return(array); } protected virtual bool TryHashFinal(Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs index 3192bd6..e1c8262 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs @@ -244,7 +244,8 @@ namespace Internal.Cryptography.Pal } finally { - ArrayPool.Shared.Return(crlDistributionPoints.Array); + // The data came from a certificate, so it's public. + CryptoPool.Return(crlDistributionPoints.Array, clearSize: 0); } return null; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs index 8037861..314cc3e 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs @@ -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.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 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.Shared.Rent(chainSize); - tempChain = tempChainRent.AsSpan(0, chainSize); + int targetSize = checked(chainSize * IntPtr.Size); + tempChainRent = CryptoPool.Rent(targetSize); + tempChain = MemoryMarshal.Cast(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.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.Shared.Return(encoded.Array); + CryptoPool.Return(encoded.Array, clearSize: 0); ArrayPool.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.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.Shared.Rent(size); // We only do spares writes. diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 182cb68..03be022 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -18,6 +18,9 @@ Common\System\Memory\PointerMemoryManager.cs + + Common\System\Security\Cryptography\CryptoPool.cs + Common\System\Security\Cryptography\Oids.cs @@ -407,6 +410,9 @@ Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.PooledCrypto.cs + Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs -- 2.7.4