Span BasicSymmetricCipher and other symmetric crypto internals
authorKevin Jones <kevin@vcsjones.com>
Fri, 10 Jul 2020 19:46:39 +0000 (15:46 -0400)
committerGitHub <noreply@github.com>
Fri, 10 Jul 2020 19:46:39 +0000 (12:46 -0700)
14 files changed:
src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipher.cs
src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs
src/libraries/Common/src/Internal/Cryptography/UniversalCryptoDecryptor.cs
src/libraries/Common/src/Internal/Cryptography/UniversalCryptoEncryptor.cs
src/libraries/Common/src/Internal/Cryptography/UniversalCryptoTransform.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptEncryptDecrypt.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AppleCCCryptor.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/OpenSslCipher.cs
src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/BasicSymmetricCipherNCrypt.cs
src/libraries/System.Security.Cryptography.Cng/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography.Csp/src/Internal/Cryptography/BasicSymmetricCipherCsp.cs
src/libraries/System.Security.Cryptography.Csp/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography.Csp/src/System.Security.Cryptography.Csp.csproj
src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/CapiHelper.Windows.cs

index 1820018..df3ae1f 100644 (file)
@@ -28,9 +28,9 @@ namespace Internal.Cryptography
             BlockSizeInBytes = blockSizeInBytes;
         }
 
-        public abstract int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset);
+        public abstract int Transform(ReadOnlySpan<byte> input, Span<byte> output);
 
-        public abstract byte[] TransformFinal(byte[] input, int inputOffset, int count);
+        public abstract int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output);
 
         public int BlockSizeInBytes { get; private set; }
 
index 91f174e..2deb97c 100644 (file)
@@ -60,28 +60,23 @@ namespace Internal.Cryptography
             base.Dispose(disposing);
         }
 
-        public override int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+        public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(count > 0);
-            Debug.Assert((count % BlockSizeInBytes) == 0);
-            Debug.Assert(input.Length - inputOffset >= count);
-            Debug.Assert(output != null);
-            Debug.Assert(outputOffset >= 0);
-            Debug.Assert(output.Length - outputOffset >= count);
+            Debug.Assert(input.Length > 0);
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
 
             int numBytesWritten;
+
             if (_encrypting)
             {
-                numBytesWritten = Interop.BCrypt.BCryptEncrypt(_hKey, input, inputOffset, count, _currentIv, output, outputOffset, output.Length - outputOffset);
+                numBytesWritten = Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output);
             }
             else
             {
-                numBytesWritten = Interop.BCrypt.BCryptDecrypt(_hKey, input, inputOffset, count, _currentIv, output, outputOffset, output.Length - outputOffset);
+                numBytesWritten = Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output);
             }
 
-            if (numBytesWritten != count)
+            if (numBytesWritten != input.Length)
             {
                 // CNG gives us no way to tell BCryptDecrypt() that we're decrypting the final block, nor is it performing any
                 // padding /depadding for us. So there's no excuse for a provider to hold back output for "future calls." Though
@@ -92,23 +87,20 @@ namespace Internal.Cryptography
             return numBytesWritten;
         }
 
-        public override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+        public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(count >= 0);
-            Debug.Assert((count % BlockSizeInBytes) == 0);
-            Debug.Assert(input.Length - inputOffset >= count);
-
-            byte[] output = new byte[count];
-            if (count != 0)
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+
+            int numBytesWritten = 0;
+
+            if (input.Length != 0)
             {
-                int numBytesWritten = Transform(input, inputOffset, count, output, 0);
-                Debug.Assert(numBytesWritten == count);  // Our implementation of Transform() guarantees this. See comment above.
+                numBytesWritten = Transform(input, output);
+                Debug.Assert(numBytesWritten == input.Length);  // Our implementation of Transform() guarantees this. See comment above.
             }
 
             Reset();
-            return output;
+            return numBytesWritten;
         }
 
         private void Reset()
index bbf2f8e..754dbc0 100644 (file)
@@ -23,7 +23,7 @@ namespace Internal.Cryptography
         {
         }
 
-        protected sealed override int UncheckedTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+        protected override int UncheckedTransformBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
         {
             //
             // If we're decrypting, it's possible to be called with the last blocks of the data, and then
@@ -41,8 +41,8 @@ namespace Internal.Cryptography
                 // If we have data saved from a previous call, decrypt that into the output first
                 if (_heldoverCipher != null)
                 {
-                    int depadDecryptLength = BasicSymmetricCipher.Transform(_heldoverCipher, 0, _heldoverCipher.Length, outputBuffer, outputOffset);
-                    outputOffset += depadDecryptLength;
+                    int depadDecryptLength = BasicSymmetricCipher.Transform(_heldoverCipher, outputBuffer);
+                    outputBuffer = outputBuffer.Slice(depadDecryptLength);
                     decryptedBytes += depadDecryptLength;
                 }
                 else
@@ -51,25 +51,24 @@ namespace Internal.Cryptography
                 }
 
                 // Postpone the last block to the next round.
-                Debug.Assert(inputCount >= _heldoverCipher.Length, "inputCount >= _heldoverCipher.Length");
-                int startOfLastBlock = inputOffset + inputCount - _heldoverCipher.Length;
-                Buffer.BlockCopy(inputBuffer, startOfLastBlock, _heldoverCipher, 0, _heldoverCipher.Length);
-                inputCount -= _heldoverCipher.Length;
-                Debug.Assert(inputCount % InputBlockSize == 0, "Did not remove whole blocks for depadding");
+                Debug.Assert(inputBuffer.Length >= _heldoverCipher.Length, "inputBuffer.Length >= _heldoverCipher.Length");
+                inputBuffer.Slice(inputBuffer.Length - _heldoverCipher.Length).CopyTo(_heldoverCipher);
+                inputBuffer = inputBuffer.Slice(0, inputBuffer.Length - _heldoverCipher.Length);
+                Debug.Assert(inputBuffer.Length % InputBlockSize == 0, "Did not remove whole blocks for depadding");
             }
 
-            if (inputCount > 0)
+            if (inputBuffer.Length > 0)
             {
-                decryptedBytes += BasicSymmetricCipher.Transform(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+                decryptedBytes += BasicSymmetricCipher.Transform(inputBuffer, outputBuffer);
             }
 
             return decryptedBytes;
         }
 
-        protected sealed override byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+        protected override unsafe int UncheckedTransformFinalBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
         {
             // We can't complete decryption on a partial block
-            if (inputCount % InputBlockSize != 0)
+            if (inputBuffer.Length % InputBlockSize != 0)
                 throw new CryptographicException(SR.Cryptography_PartialBlock);
 
             //
@@ -77,46 +76,91 @@ namespace Internal.Cryptography
             // Otherwise the decryption buffer is just the input data.
             //
 
-            byte[]? ciphertext = null;
+            ReadOnlySpan<byte> inputCiphertext;
+            Span<byte> ciphertext;
+            byte[]? rentedCiphertext = null;
+            int rentedCiphertextSize = 0;
 
-            if (_heldoverCipher == null)
+            try
             {
-                ciphertext = new byte[inputCount];
-                Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, 0, inputCount);
+                if (_heldoverCipher == null)
+                {
+                    rentedCiphertextSize = inputBuffer.Length;
+                    rentedCiphertext = CryptoPool.Rent(inputBuffer.Length);
+                    ciphertext = rentedCiphertext.AsSpan(0, inputBuffer.Length);
+                    inputCiphertext = inputBuffer;
+                }
+                else
+                {
+                    rentedCiphertextSize = _heldoverCipher.Length + inputBuffer.Length;
+                    rentedCiphertext = CryptoPool.Rent(rentedCiphertextSize);
+                    ciphertext = rentedCiphertext.AsSpan(0, rentedCiphertextSize);
+                    _heldoverCipher.AsSpan().CopyTo(ciphertext);
+                    inputBuffer.CopyTo(ciphertext.Slice(_heldoverCipher.Length));
+
+                    // Decrypt in-place
+                    inputCiphertext = ciphertext;
+                }
+
+                int unpaddedLength = 0;
+
+                fixed (byte* pCiphertext = ciphertext)
+                {
+                    // Decrypt the data, then strip the padding to get the final decrypted data. Note that even if the cipherText length is 0, we must
+                    // invoke TransformFinal() so that the cipher object knows to reset for the next cipher operation.
+                    int decryptWritten = BasicSymmetricCipher.TransformFinal(inputCiphertext, ciphertext);
+                    Span<byte> decryptedBytes = ciphertext.Slice(0, decryptWritten);
+
+                    if (decryptedBytes.Length > 0)
+                    {
+                        unpaddedLength = GetPaddingLength(decryptedBytes);
+                        decryptedBytes.Slice(0, unpaddedLength).CopyTo(outputBuffer);
+                    }
+                }
+
+                Reset();
+                return unpaddedLength;
             }
-            else
+            finally
             {
-                ciphertext = new byte[_heldoverCipher.Length + inputCount];
-                Buffer.BlockCopy(_heldoverCipher, 0, ciphertext, 0, _heldoverCipher.Length);
-                Buffer.BlockCopy(inputBuffer, inputOffset, ciphertext, _heldoverCipher.Length, inputCount);
+                if (rentedCiphertext != null)
+                {
+                    CryptoPool.Return(rentedCiphertext, clearSize: rentedCiphertextSize);
+                }
             }
+        }
 
-            // Decrypt the data, then strip the padding to get the final decrypted data. Note that even if the cipherText length is 0, we must
-            // invoke TransformFinal() so that the cipher object knows to reset for the next cipher operation.
-            byte[] decryptedBytes = BasicSymmetricCipher.TransformFinal(ciphertext, 0, ciphertext.Length);
-            byte[] outputData;
-            if (ciphertext.Length > 0)
+        protected override unsafe byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+        {
+            if (DepaddingRequired)
             {
-                unsafe
+                byte[] rented = CryptoPool.Rent(inputCount + InputBlockSize);
+                int written = 0;
+
+                fixed (byte* pRented = rented)
                 {
-                    fixed (byte* decryptedBytesPtr = decryptedBytes)
+                    try
                     {
-                        outputData = DepadBlock(decryptedBytes, 0, decryptedBytes.Length);
-
-                        if (outputData != decryptedBytes)
-                        {
-                            CryptographicOperations.ZeroMemory(decryptedBytes);
-                        }
+                        written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), rented);
+                        return rented.AsSpan(0, written).ToArray();
+                    }
+                    finally
+                    {
+                        CryptoPool.Return(rented, clearSize: written);
                     }
                 }
             }
             else
             {
-                outputData = Array.Empty<byte>();
+#if NETSTANDARD || NETFRAMEWORK || NETCOREAPP3_0
+                byte[] buffer = new byte[inputCount];
+#else
+                byte[] buffer = GC.AllocateUninitializedArray<byte>(inputCount);
+#endif
+                int written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), buffer);
+                Debug.Assert(written == buffer.Length);
+                return buffer;
             }
-
-            Reset();
-            return outputData;
         }
 
         protected sealed override void Dispose(bool disposing)
@@ -165,21 +209,18 @@ namespace Internal.Cryptography
         }
 
         /// <summary>
-        ///     Remove the padding from the last blocks being decrypted
+        ///     Gets the length of the padding applied to the block, and validates
+        ///     the padding, if possible.
         /// </summary>
-        private byte[] DepadBlock(byte[] block, int offset, int count)
+        private int GetPaddingLength(ReadOnlySpan<byte> block)
         {
-            Debug.Assert(block != null && count >= block.Length - offset);
-            Debug.Assert(0 <= offset);
-            Debug.Assert(0 <= count);
-
             int padBytes = 0;
 
             // See PadBlock for a description of the padding modes.
             switch (PaddingMode)
             {
                 case PaddingMode.ANSIX923:
-                    padBytes = block[offset + count - 1];
+                    padBytes = block[^1];
 
                     // Verify the amount of padding is reasonable
                     if (padBytes <= 0 || padBytes > InputBlockSize)
@@ -188,7 +229,7 @@ namespace Internal.Cryptography
                     }
 
                     // Verify that all the padding bytes are 0s
-                    for (int i = offset + count - padBytes; i < offset + count - 1; i++)
+                    for (int i = block.Length - padBytes; i < block.Length - 1; i++)
                     {
                         if (block[i] != 0)
                         {
@@ -199,7 +240,7 @@ namespace Internal.Cryptography
                     break;
 
                 case PaddingMode.ISO10126:
-                    padBytes = block[offset + count - 1];
+                    padBytes = block[^1];
 
                     // Verify the amount of padding is reasonable
                     if (padBytes <= 0 || padBytes > InputBlockSize)
@@ -211,14 +252,14 @@ namespace Internal.Cryptography
                     break;
 
                 case PaddingMode.PKCS7:
-                    padBytes = block[offset + count - 1];
+                    padBytes = block[^1];
 
                     // Verify the amount of padding is reasonable
                     if (padBytes <= 0 || padBytes > InputBlockSize)
                         throw new CryptographicException(SR.Cryptography_InvalidPadding);
 
                     // Verify all the padding bytes match the amount of padding
-                    for (int i = offset + count - padBytes; i < offset + count; i++)
+                    for (int i = block.Length - padBytes; i < block.Length - 1; i++)
                     {
                         if (block[i] != padBytes)
                             throw new CryptographicException(SR.Cryptography_InvalidPadding);
@@ -237,10 +278,7 @@ namespace Internal.Cryptography
                     throw new CryptographicException(SR.Cryptography_UnknownPaddingMode);
             }
 
-            // Copy everything but the padding to the output
-            byte[] depadded = new byte[count - padBytes];
-            Buffer.BlockCopy(block, offset, depadded, 0, depadded.Length);
-            return depadded;
+            return block.Length - padBytes;
         }
 
         //
index 702c9de..2bf431a 100644 (file)
@@ -22,81 +22,134 @@ namespace Internal.Cryptography
         {
         }
 
-        protected sealed override int UncheckedTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+        protected override int UncheckedTransformBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
         {
-            return BasicSymmetricCipher.Transform(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+            return BasicSymmetricCipher.Transform(inputBuffer, outputBuffer);
         }
 
-        protected sealed override unsafe byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+        protected override int UncheckedTransformFinalBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)
         {
-            byte[] paddedBlock = PadBlock(inputBuffer, inputOffset, inputCount);
+            // The only caller of this method is the array-allocating overload, outputBuffer is
+            // always new memory, not a user-provided buffer.
+            Debug.Assert(!inputBuffer.Overlaps(outputBuffer));
 
-            fixed (byte* paddedBlockPtr = paddedBlock)
-            {
-                byte[] output = BasicSymmetricCipher.TransformFinal(paddedBlock, 0, paddedBlock.Length);
+            int padWritten = PadBlock(inputBuffer, outputBuffer);
+            int transformWritten = BasicSymmetricCipher.TransformFinal(outputBuffer.Slice(0, padWritten), outputBuffer);
+
+            // After padding, we should have an even number of blocks, and the same applies
+            // to the transform.
+            Debug.Assert(padWritten == transformWritten);
+
+            return transformWritten;
+        }
+
+        protected override byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+        {
+            byte[] buffer;
+#if NETSTANDARD || NETFRAMEWORK || NETCOREAPP3_0
+            buffer = new byte[GetCiphertextLength(inputCount)];
+#else
+            buffer = GC.AllocateUninitializedArray<byte>(GetCiphertextLength(inputCount));
+#endif
+            int written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), buffer);
+            Debug.Assert(written == buffer.Length);
+            return buffer;
+        }
+
+        private int GetCiphertextLength(int plaintextLength)
+        {
+            Debug.Assert(plaintextLength >= 0);
 
-                if (paddedBlock != inputBuffer)
-                {
-                    CryptographicOperations.ZeroMemory(paddedBlock);
-                }
+             //divisor and factor are same and won't overflow.
+            int wholeBlocks = Math.DivRem(plaintextLength, InputBlockSize, out int remainder) * InputBlockSize;
 
-                return output;
+            switch (PaddingMode)
+            {
+                case PaddingMode.None when (remainder != 0):
+                    throw new CryptographicException(SR.Cryptography_PartialBlock);
+                case PaddingMode.None:
+                case PaddingMode.Zeros when (remainder == 0):
+                    return plaintextLength;
+                case PaddingMode.Zeros:
+                case PaddingMode.PKCS7:
+                case PaddingMode.ANSIX923:
+                case PaddingMode.ISO10126:
+                    return checked(wholeBlocks + InputBlockSize);
+                default:
+                    Debug.Fail($"Unknown padding mode {PaddingMode}.");
+                    throw new CryptographicException(SR.Cryptography_UnknownPaddingMode);
             }
         }
 
-        private byte[] PadBlock(byte[] block, int offset, int count)
+        private int PadBlock(ReadOnlySpan<byte> block, Span<byte> destination)
         {
-            byte[] result;
-            int padBytes = InputBlockSize - (count % InputBlockSize);
+            int count = block.Length;
+            int paddingRemainder = count % InputBlockSize;
+            int padBytes = InputBlockSize - paddingRemainder;
 
             switch (PaddingMode)
             {
+                case PaddingMode.None when (paddingRemainder != 0):
+                    throw new CryptographicException(SR.Cryptography_PartialBlock);
+
                 case PaddingMode.None:
-                    if (count % InputBlockSize != 0)
-                        throw new CryptographicException(SR.Cryptography_PartialBlock);
+                    if (destination.Length < count)
+                    {
+                        throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+                    }
 
-                    result = new byte[count];
-                    Buffer.BlockCopy(block, offset, result, 0, result.Length);
-                    break;
+                    block.CopyTo(destination);
+                    return count;
 
                 // ANSI padding fills the blocks with zeros and adds the total number of padding bytes as
                 // the last pad byte, adding an extra block if the last block is complete.
                 //
-                // x 00 00 00 00 00 00 07
+                // xx 00 00 00 00 00 00 07
                 case PaddingMode.ANSIX923:
-                    result = new byte[count + padBytes];
+                    int ansiSize = count + padBytes;
 
-                    Buffer.BlockCopy(block, offset, result, 0, count);
-                    result[result.Length - 1] = (byte)padBytes;
+                    if (destination.Length < ansiSize)
+                    {
+                        throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+                    }
 
-                    break;
+                    block.CopyTo(destination);
+                    destination.Slice(count, padBytes - 1).Clear();
+                    destination[count + padBytes - 1] = (byte)padBytes;
+                    return ansiSize;
 
                 // ISO padding fills the blocks up with random bytes and adds the total number of padding
                 // bytes as the last pad byte, adding an extra block if the last block is complete.
                 //
                 // xx rr rr rr rr rr rr 07
                 case PaddingMode.ISO10126:
-                    result = new byte[count + padBytes];
+                    int isoSize = count + padBytes;
 
-                    Buffer.BlockCopy(block, offset, result, 0, count);
-                    RandomNumberGenerator.Fill(result.AsSpan(count + 1, padBytes - 1));
-                    result[result.Length - 1] = (byte)padBytes;
+                    if (destination.Length < isoSize)
+                    {
+                        throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+                    }
 
-                    break;
+                    block.CopyTo(destination);
+                    RandomNumberGenerator.Fill(destination.Slice(count, padBytes - 1));
+                    destination[count + padBytes - 1] = (byte)padBytes;
+                    return isoSize;
 
                 // PKCS padding fills the blocks up with bytes containing the total number of padding bytes
                 // used, adding an extra block if the last block is complete.
                 //
                 // xx xx 06 06 06 06 06 06
                 case PaddingMode.PKCS7:
-                    result = new byte[count + padBytes];
-                    Buffer.BlockCopy(block, offset, result, 0, count);
+                    int pkcsSize = count + padBytes;
 
-                    for (int i = count; i < result.Length; i++)
+                    if (destination.Length < pkcsSize)
                     {
-                        result[i] = (byte)padBytes;
+                        throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
                     }
-                    break;
+
+                    block.CopyTo(destination);
+                    destination.Slice(count, padBytes).Fill((byte)padBytes);
+                    return pkcsSize;
 
                 // Zeros padding fills the last partial block with zeros, and does not add a new block to
                 // the end if the last block is already complete.
@@ -108,15 +161,20 @@ namespace Internal.Cryptography
                         padBytes = 0;
                     }
 
-                    result = new byte[count + padBytes];
-                    Buffer.BlockCopy(block, offset, result, 0, count);
-                    break;
+                    int zeroSize = count + padBytes;
+
+                    if (destination.Length < zeroSize)
+                    {
+                        throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+                    }
+
+                    destination.Slice(0, zeroSize).Clear();
+                    block.CopyTo(destination);
+                    return zeroSize;
 
                 default:
                     throw new CryptographicException(SR.Cryptography_UnknownPaddingMode);
             }
-
-            return result;
         }
     }
 }
index c8f1cb5..b4406e5 100644 (file)
@@ -108,8 +108,17 @@ namespace Internal.Cryptography
             }
         }
 
-        protected abstract int UncheckedTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
+        protected int UncheckedTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+        {
+            return UncheckedTransformBlock(inputBuffer.AsSpan(inputOffset, inputCount), outputBuffer.AsSpan(outputOffset));
+        }
+
+        protected abstract int UncheckedTransformBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer);
+
+        // For final block, encryption and decryption can give better context for the returning byte size, so we
+        // don't provide an implementation here.
         protected abstract byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount);
+        protected abstract int UncheckedTransformFinalBlock(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer);
 
         protected PaddingMode PaddingMode { get; private set; }
         protected BasicSymmetricCipher BasicSymmetricCipher { get; private set; }
index 1069b4c..d365f4e 100644 (file)
@@ -13,57 +13,43 @@ internal static partial class Interop
     internal static partial class BCrypt
     {
         // Note: input and output are allowed to be the same buffer. BCryptEncrypt will correctly do the encryption in place according to CNG documentation.
-        internal static int BCryptEncrypt(SafeKeyHandle hKey, byte[] input, int inputOffset, int inputCount, byte[]? iv, byte[] output, int outputOffset, int outputCount)
+        internal static int BCryptEncrypt(SafeKeyHandle hKey, ReadOnlySpan<byte> input, byte[]? iv, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(inputCount >= 0);
-            Debug.Assert(inputCount <= input.Length - inputOffset);
-            Debug.Assert(output != null);
-            Debug.Assert(outputOffset >= 0);
-            Debug.Assert(outputCount >= 0);
-            Debug.Assert(outputCount <= output.Length - outputOffset);
-
             unsafe
             {
                 fixed (byte* pbInput = input)
+                fixed (byte* pbOutput = output)
                 {
-                    fixed (byte* pbOutput = output)
+                    int cbResult;
+                    NTSTATUS ntStatus = BCryptEncrypt(hKey, pbInput, input.Length, IntPtr.Zero, iv, iv == null ? 0 : iv.Length, pbOutput, output.Length, out cbResult, 0);
+
+                    if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                     {
-                        int cbResult;
-                        NTSTATUS ntStatus = BCryptEncrypt(hKey, pbInput + inputOffset, inputCount, IntPtr.Zero, iv, iv == null ? 0 : iv.Length, pbOutput + outputOffset, outputCount, out cbResult, 0);
-                        if (ntStatus != NTSTATUS.STATUS_SUCCESS)
-                            throw CreateCryptographicException(ntStatus);
-                        return cbResult;
+                        throw CreateCryptographicException(ntStatus);
                     }
+
+                    return cbResult;
                 }
             }
         }
 
         // Note: input and output are allowed to be the same buffer. BCryptDecrypt will correctly do the decryption in place according to CNG documentation.
-        internal static int BCryptDecrypt(SafeKeyHandle hKey, byte[] input, int inputOffset, int inputCount, byte[]? iv, byte[] output, int outputOffset, int outputCount)
+        internal static int BCryptDecrypt(SafeKeyHandle hKey, ReadOnlySpan<byte> input, byte[]? iv, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(inputCount >= 0);
-            Debug.Assert(inputCount <= input.Length - inputOffset);
-            Debug.Assert(output != null);
-            Debug.Assert(outputOffset >= 0);
-            Debug.Assert(outputCount >= 0);
-            Debug.Assert(outputCount <= output.Length - outputOffset);
-
             unsafe
             {
                 fixed (byte* pbInput = input)
+                fixed (byte* pbOutput = output)
                 {
-                    fixed (byte* pbOutput = output)
+                    int cbResult;
+                    NTSTATUS ntStatus = BCryptDecrypt(hKey, pbInput, input.Length, IntPtr.Zero, iv, iv == null ? 0 : iv.Length, pbOutput, output.Length, out cbResult, 0);
+
+                    if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                     {
-                        int cbResult;
-                        NTSTATUS ntStatus = BCryptDecrypt(hKey, pbInput + inputOffset, inputCount, IntPtr.Zero, iv, iv == null ? 0 : iv.Length, pbOutput + outputOffset, outputCount, out cbResult, 0);
-                        if (ntStatus != NTSTATUS.STATUS_SUCCESS)
-                            throw CreateCryptographicException(ntStatus);
-                        return cbResult;
+                        throw CreateCryptographicException(ntStatus);
                     }
+
+                    return cbResult;
                 }
             }
         }
index 4b48069..a584e1d 100644 (file)
@@ -38,46 +38,38 @@ namespace Internal.Cryptography
             base.Dispose(disposing);
         }
 
-        public override int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+        public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null, "Expected valid input, got null");
-            Debug.Assert(inputOffset >= 0, $"Expected non-negative inputOffset, got {inputOffset}");
-            Debug.Assert(count > 0, $"Expected positive count, got {count}");
-            Debug.Assert((count % BlockSizeInBytes) == 0, $"Expected count aligned to block size {BlockSizeInBytes}, got {count}");
-            Debug.Assert(input.Length - inputOffset >= count, $"Expected valid input length/offset/count triplet, got {input.Length}/{inputOffset}/{count}");
-            Debug.Assert(output != null, "Expected valid output, got null");
-            Debug.Assert(outputOffset >= 0, $"Expected non-negative outputOffset, got {outputOffset}");
-            Debug.Assert(output.Length - outputOffset >= count, $"Expected valid output length/offset/count triplet, got {output.Length}/{outputOffset}/{count}");
-
-            return CipherUpdate(input, inputOffset, count, output, outputOffset);
+            Debug.Assert(input.Length > 0);
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+
+            return CipherUpdate(input, output);
         }
 
-        public override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+        public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null, "Expected valid input, got null");
-            Debug.Assert(inputOffset >= 0, $"Expected non-negative inputOffset, got {inputOffset}");
-            Debug.Assert(count >= 0, $"Expected non-negative count, got {count}");
-            Debug.Assert((count % BlockSizeInBytes) == 0, $"Expected count aligned to block size {BlockSizeInBytes}, got {count}");
-            Debug.Assert(input.Length - inputOffset >= count, $"Expected valid input length/offset/count triplet, got {input.Length}/{inputOffset}/{count}");
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert(input.Length <= output.Length);
 
-            byte[] output = ProcessFinalBlock(input, inputOffset, count);
+            int written = ProcessFinalBlock(input, output);
             Reset();
-            return output;
+            return written;
         }
 
-        private unsafe byte[] ProcessFinalBlock(byte[] input, int inputOffset, int count)
+        private unsafe int ProcessFinalBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            if (count == 0)
+            if (input.Length == 0)
             {
-                return Array.Empty<byte>();
+                return 0;
             }
 
-            byte[] output = new byte[count];
-            int outputBytes = CipherUpdate(input, inputOffset, count, output, 0);
+            int outputBytes = CipherUpdate(input, output);
             int ret;
             int errorCode;
 
-            fixed (byte* outputStart = &output[0])
+            Debug.Assert(output.Length > 0);
+
+            fixed (byte* outputStart = output)
             {
                 byte* outputCurrent = outputStart + outputBytes;
                 int bytesWritten;
@@ -94,44 +86,29 @@ namespace Internal.Cryptography
 
             ProcessInteropError(ret, errorCode);
 
-            if (outputBytes == output.Length)
-            {
-                return output;
-            }
-
-            if (outputBytes == 0)
-            {
-                return Array.Empty<byte>();
-            }
-
-            byte[] userData = new byte[outputBytes];
-            Buffer.BlockCopy(output, 0, userData, 0, outputBytes);
-            return userData;
+            return outputBytes;
         }
 
-        private unsafe int CipherUpdate(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+        private unsafe int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output)
         {
             int ret;
             int ccStatus;
             int bytesWritten;
 
-            if (count == 0)
+            if (input.Length == 0)
             {
                 return 0;
             }
 
-            fixed (byte* inputStart = input)
-            fixed (byte* outputStart = output)
+            fixed (byte* pInput = input)
+            fixed (byte* pOutput = output)
             {
-                byte* inputCurrent = inputStart + inputOffset;
-                byte* outputCurrent = outputStart + outputOffset;
-
                 ret = Interop.AppleCrypto.CryptorUpdate(
                     _cryptor,
-                    inputCurrent,
-                    count,
-                    outputCurrent,
-                    output.Length - outputOffset,
+                    pInput,
+                    input.Length,
+                    pOutput,
+                    output.Length,
                     out bytesWritten,
                     out ccStatus);
             }
index 01f3dc4..49dc42d 100644 (file)
@@ -40,27 +40,22 @@ namespace Internal.Cryptography
             base.Dispose(disposing);
         }
 
-        public override unsafe int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+        public override unsafe int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(count > 0);
-            Debug.Assert((count % BlockSizeInBytes) == 0);
-            Debug.Assert(input.Length - inputOffset >= count);
-            Debug.Assert(output != null);
-            Debug.Assert(outputOffset >= 0);
-            Debug.Assert(output.Length - outputOffset >= count);
+            Debug.Assert(input.Length > 0);
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
 
             // OpenSSL 1.1 does not allow partial overlap.
-            if (input == output && inputOffset != outputOffset)
+            if (input.Overlaps(output, out int overlapOffset) && overlapOffset != 0)
             {
-                byte[] tmp = CryptoPool.Rent(count);
+                byte[] tmp = CryptoPool.Rent(input.Length);
+                Span<byte> tmpSpan = tmp;
                 int written = 0;
 
                 try
                 {
-                    written = CipherUpdate(input, inputOffset, count, tmp, 0);
-                    Buffer.BlockCopy(tmp, 0, output, outputOffset, written);
+                    written = CipherUpdate(input, tmpSpan);
+                    tmpSpan.Slice(0, written).CopyTo(output);
                     return written;
                 }
                 finally
@@ -69,59 +64,59 @@ namespace Internal.Cryptography
                 }
             }
 
-            return CipherUpdate(input, inputOffset, count, output, outputOffset);
+            return CipherUpdate(input, output);
         }
 
-        public override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+        public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(count >= 0);
-            Debug.Assert((count % BlockSizeInBytes) == 0);
-            Debug.Assert(input.Length - inputOffset >= count);
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+            Debug.Assert(input.Length <= output.Length);
 
-            byte[] output = ProcessFinalBlock(input, inputOffset, count);
+            int written = ProcessFinalBlock(input, output);
             Reset();
-            return output;
+            return written;
         }
 
-        private byte[] ProcessFinalBlock(byte[] input, int inputOffset, int count)
+        private int ProcessFinalBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            byte[] output = new byte[count];
-            int outputBytes = CipherUpdate(input, inputOffset, count, output, 0);
-
-            Span<byte> outputSpan = output.AsSpan(outputBytes);
-            int bytesWritten;
-            CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out bytesWritten));
-            outputBytes += bytesWritten;
-
-            if (outputBytes == output.Length)
+            // If input and output overlap but are not the same, we need to use a
+            // temp buffer since openssl doesn't seem to like partial overlaps.
+            if (input.Overlaps(output, out int offset) && offset != 0)
             {
-                return output;
-            }
+                byte[] rented = CryptoPool.Rent(input.Length);
+                int written = 0;
 
-            if (outputBytes == 0)
+                try
+                {
+                    written = CipherUpdate(input, rented);
+                    Span<byte> outputSpan = rented.AsSpan(written);
+                    CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out int finalWritten));
+                    written += finalWritten;
+                    rented.AsSpan(0, written).CopyTo(output);
+                    return written;
+                }
+                finally
+                {
+                    CryptoPool.Return(rented, clearSize: written);
+                }
+            }
+            else
             {
-                return Array.Empty<byte>();
+                int written = CipherUpdate(input, output);
+                Span<byte> outputSpan = output.Slice(written);
+                CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out int finalWritten));
+                written += finalWritten;
+                return written;
             }
-
-            byte[] userData = new byte[outputBytes];
-            Buffer.BlockCopy(output, 0, userData, 0, outputBytes);
-            return userData;
         }
 
-        private int CipherUpdate(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+        private int CipherUpdate(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            int bytesWritten;
-
-            ReadOnlySpan<byte> inputSpan = input.AsSpan(inputOffset, count);
-            Span<byte> outputSpan = output.AsSpan(outputOffset);
-
             Interop.Crypto.EvpCipherUpdate(
                 _ctx,
-                outputSpan,
-                out bytesWritten,
-                inputSpan);
+                output,
+                out int bytesWritten,
+                input);
 
             return bytesWritten;
         }
index b84069c..4775435 100644 (file)
@@ -37,28 +37,20 @@ namespace Internal.Cryptography
             Reset();
         }
 
-        public sealed override int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+        public sealed override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(count > 0);
-            Debug.Assert((count % BlockSizeInBytes) == 0);
-            Debug.Assert(input.Length - inputOffset >= count);
-            Debug.Assert(output != null);
-            Debug.Assert(outputOffset >= 0);
-            Debug.Assert(output.Length - outputOffset >= count);
+            Debug.Assert(input.Length > 0);
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
 
             int numBytesWritten;
             ErrorCode errorCode;
             using (SafeNCryptKeyHandle keyHandle = _cngKey!.Handle)
             {
-                var inputSpan = new ReadOnlySpan<byte>(input, inputOffset, count);
-                var outputSpan = new Span<byte>(output, outputOffset, count);
                 unsafe
                 {
                     errorCode = _encrypting ?
-                        Interop.NCrypt.NCryptEncrypt(keyHandle, inputSpan, inputSpan.Length, null, outputSpan, outputSpan.Length, out numBytesWritten, AsymmetricPaddingMode.None) :
-                        Interop.NCrypt.NCryptDecrypt(keyHandle, inputSpan, inputSpan.Length, null, outputSpan, outputSpan.Length, out numBytesWritten, AsymmetricPaddingMode.None);
+                        Interop.NCrypt.NCryptEncrypt(keyHandle, input, input.Length, null, output, output.Length, out numBytesWritten, AsymmetricPaddingMode.None) :
+                        Interop.NCrypt.NCryptDecrypt(keyHandle, input, input.Length, null, output, output.Length, out numBytesWritten, AsymmetricPaddingMode.None);
                 }
             }
             if (errorCode != ErrorCode.ERROR_SUCCESS)
@@ -66,7 +58,7 @@ namespace Internal.Cryptography
                 throw errorCode.ToCryptographicException();
             }
 
-            if (numBytesWritten != count)
+            if (numBytesWritten != input.Length)
             {
                 // CNG gives us no way to tell NCryptDecrypt() that we're decrypting the final block, nor is it performing any padding/depadding for us.
                 // So there's no excuse for a provider to hold back output for "future calls." Though this isn't technically our problem to detect, we might as well
@@ -77,23 +69,20 @@ namespace Internal.Cryptography
             return numBytesWritten;
         }
 
-        public sealed override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+        public sealed override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(count >= 0);
-            Debug.Assert((count % BlockSizeInBytes) == 0);
-            Debug.Assert(input.Length - inputOffset >= count);
-
-            byte[] output = new byte[count];
-            if (count != 0)
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+
+            int numBytesWritten = 0;
+
+            if (input.Length != 0)
             {
-                int numBytesWritten = Transform(input, inputOffset, count, output, 0);
-                Debug.Assert(numBytesWritten == count);  // Our implementation of Transform() guarantees this. See comment above.
+                numBytesWritten = Transform(input, output);
+                Debug.Assert(numBytesWritten == input.Length);  // Our implementation of Transform() guarantees this. See comment above.
             }
 
             Reset();
-            return output;
+            return numBytesWritten;
         }
 
         protected sealed override void Dispose(bool disposing)
index 57d0db4..567cfde 100644 (file)
   <data name="Cryptography_InvalidECCharacteristic2Curve" xml:space="preserve">
     <value>The specified Characteristic2 curve parameters are not valid. Polynomial, A, B, G.X, G.Y, and Order are required. A, B, G.X, G.Y must be the same length, and the same length as Q.X, Q.Y and D if those are specified. Seed, Cofactor and Hash are optional. Other parameters are not allowed.</value>
   </data>
+  <data name="Argument_DestinationTooShort" xml:space="preserve">
+    <value>Destination is too short.</value>
+  </data>
 </root>
index 82a820a..75864f8 100644 (file)
@@ -60,56 +60,47 @@ namespace Internal.Cryptography
             base.Dispose(disposing);
         }
 
-        public override int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
+        public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            return Transform(input, inputOffset, count, output, outputOffset, false);
+            return Transform(input, output, false);
         }
 
-        public override byte[] TransformFinal(byte[] input, int inputOffset, int count)
+        public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(count >= 0);
-            Debug.Assert((count % BlockSizeInBytes) == 0);
-            Debug.Assert(input.Length - inputOffset >= count);
-
-            byte[] output = new byte[count];
-            if (count != 0)
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
+
+            int numBytesWritten = 0;
+
+            if (input.Length != 0)
             {
-                int numBytesWritten = Transform(input, inputOffset, count, output, 0, true);
-                Debug.Assert(numBytesWritten == count);  // Our implementation of Transform() guarantees this.
+                numBytesWritten = Transform(input, output, true);
+                Debug.Assert(numBytesWritten == input.Length);  // Our implementation of Transform() guarantees this.
             }
 
             Reset();
 
-            return output;
+            return numBytesWritten;
         }
 
         private void Reset()
         {
             // Ensure we've called CryptEncrypt with the final=true flag so the handle is reset property
-            EncryptData(_hKey, Array.Empty<byte>(), 0, 0, Array.Empty<byte>(), 0, 0, true);
+            EncryptData(_hKey, default, default, true);
         }
 
-        private int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset, bool isFinal)
+        private int Transform(ReadOnlySpan<byte> input, Span<byte> output, bool isFinal)
         {
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(count > 0);
-            Debug.Assert((count % BlockSizeInBytes) == 0);
-            Debug.Assert(input.Length - inputOffset >= count);
-            Debug.Assert(output != null);
-            Debug.Assert(outputOffset >= 0);
-            Debug.Assert(output.Length - outputOffset >= count);
+            Debug.Assert(input.Length > 0);
+            Debug.Assert((input.Length % BlockSizeInBytes) == 0);
 
             int numBytesWritten;
             if (_encrypting)
             {
-                numBytesWritten = EncryptData(_hKey, input, inputOffset, count, output, outputOffset, output.Length - outputOffset, isFinal);
+                numBytesWritten = EncryptData(_hKey, input, output, isFinal);
             }
             else
             {
-                numBytesWritten = DecryptData(_hKey, input, inputOffset, count, output, outputOffset, output.Length - outputOffset);
+                numBytesWritten = DecryptData(_hKey, input, output);
             }
 
             return numBytesWritten;
index 382c9c5..ed258ad 100644 (file)
@@ -1,16 +1,16 @@
 <root>
-  <!-- 
-    Microsoft ResX Schema 
-    
+  <!--
+    Microsoft ResX Schema
+
     Version 2.0
-    
-    The primary goals of this format is to allow a simple XML format 
-    that is mostly human readable. The generation and parsing of the 
-    various data types are done through the TypeConverter classes 
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
     associated with the data types.
-    
+
     Example:
-    
+
     ... ado.net/XML headers & schema ...
     <resheader name="resmimetype">text/microsoft-resx</resheader>
     <resheader name="version">2.0</resheader>
         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
         <comment>This is a comment</comment>
     </data>
-                
-    There are any number of "resheader" rows that contain simple 
+
+    There are any number of "resheader" rows that contain simple
     name/value pairs.
-    
-    Each data row contains a name, and value. The row also contains a 
-    type or mimetype. Type corresponds to a .NET class that support 
-    text/value conversion through the TypeConverter architecture. 
-    Classes that don't support this are serialized and stored with the 
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
     mimetype set.
-    
-    The mimetype is used for serialized objects, and tells the 
-    ResXResourceReader how to depersist the object. This is currently not 
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
     extensible. For a given mimetype the value must be set accordingly:
-    
-    Note - application/x-microsoft.net.object.binary.base64 is the format 
-    that the ResXResourceWriter will generate, however the reader can 
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
     read any of the formats listed below.
-    
+
     mimetype: application/x-microsoft.net.object.binary.base64
-    value   : The object must be serialized with 
+    value   : The object must be serialized with
             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
             : and then encoded with base64 encoding.
-    
+
     mimetype: application/x-microsoft.net.object.soap.base64
-    value   : The object must be serialized with 
+    value   : The object must be serialized with
             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
             : and then encoded with base64 encoding.
 
     mimetype: application/x-microsoft.net.object.bytearray.base64
-    value   : The object must be serialized into a byte array 
+    value   : The object must be serialized into a byte array
             : using a System.ComponentModel.TypeConverter
             : and then encoded with base64 encoding.
     -->
   <data name="Cryptography_PlatformNotSupported" xml:space="preserve">
     <value>System.Security.Cryptography is not supported on this platform.</value>
   </data>
+  <data name="Argument_DestinationTooShort" xml:space="preserve">
+    <value>Destination is too short.</value>
+  </data>
 </root>
index 0b931f2..11f55d0 100644 (file)
@@ -30,6 +30,8 @@
              Link="Internal\Cryptography\Helpers.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\KeySizeHelpers.cs"
              Link="Common\System\Security\Cryptography\KeySizeHelpers.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\CryptoPool.cs"
+             Link="Common\System\Security\Cryptography\CryptoPool.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
     <Compile Include="System\Security\Cryptography\CapiHelper.Unix.cs" />
index ce41e35..ea59376 100644 (file)
@@ -818,13 +818,13 @@ namespace Internal.NativeCrypto
             Debug.Assert(encryptedData != null, "Encrypted Data is null");
             Debug.Assert(encryptedDataLength >= 0, "Encrypted data length is less than 0");
 
-            byte[] dataTobeDecrypted = new byte[encryptedDataLength];
-            Buffer.BlockCopy(encryptedData, 0, dataTobeDecrypted, 0, encryptedDataLength);
-            Array.Reverse(dataTobeDecrypted);
+            byte[] dataToBeDecrypted = new byte[encryptedDataLength];
+            Buffer.BlockCopy(encryptedData, 0, dataToBeDecrypted, 0, encryptedDataLength);
+            Array.Reverse(dataToBeDecrypted);
 
             int dwFlags = fOAEP ? (int)Interop.Advapi32.CryptDecryptFlags.CRYPT_OAEP : 0;
             int decryptedDataLength = encryptedDataLength;
-            if (!Interop.Advapi32.CryptDecrypt(safeKeyHandle, SafeHashHandle.InvalidHandle, true, dwFlags, dataTobeDecrypted, ref decryptedDataLength))
+            if (!Interop.Advapi32.CryptDecrypt(safeKeyHandle, SafeHashHandle.InvalidHandle, true, dwFlags, dataToBeDecrypted, ref decryptedDataLength))
             {
                 int ErrCode = GetErrorCode();
                 // If we're using OAEP mode and we received an NTE_BAD_FLAGS error, then OAEP is not supported on
@@ -852,7 +852,7 @@ namespace Internal.NativeCrypto
 
 
             decryptedData = new byte[decryptedDataLength];
-            Buffer.BlockCopy(dataTobeDecrypted, 0, decryptedData, 0, decryptedDataLength);
+            Buffer.BlockCopy(dataToBeDecrypted, 0, decryptedData, 0, decryptedDataLength);
             return;
         }
 
@@ -905,27 +905,15 @@ namespace Internal.NativeCrypto
 
         internal static int EncryptData(
             SafeKeyHandle hKey,
-            byte[] input,
-            int inputOffset,
-            int inputCount,
-            byte[] output,
-            int outputOffset,
-            int outputCount,
+            ReadOnlySpan<byte> input,
+            Span<byte> output,
             bool isFinal)
         {
             VerifyValidHandle(hKey);
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(inputCount >= 0);
-            Debug.Assert(inputCount <= input.Length - inputOffset);
-            Debug.Assert(output != null);
-            Debug.Assert(outputOffset >= 0);
-            Debug.Assert(outputCount >= 0);
-            Debug.Assert(outputCount <= output.Length - outputOffset);
-            Debug.Assert((inputCount % 8) == 0);
+            Debug.Assert((input.Length % 8) == 0);
 
             // Figure out how big the encrypted data will be
-            int cbEncryptedData = inputCount;
+            int cbEncryptedData = input.Length;
             if (!Interop.Advapi32.CryptEncrypt(hKey, SafeHashHandle.InvalidHandle, isFinal, 0, null, ref cbEncryptedData, cbEncryptedData))
             {
                 throw GetErrorCode().ToCryptographicException();
@@ -934,67 +922,47 @@ namespace Internal.NativeCrypto
             // encryptedData is an in/out buffer for CryptEncrypt. Allocate space for the encrypted data, and copy the
             // plaintext data into that space.  Since encrypted data will have padding applied, the size of the encrypted
             // data should always be larger than the plaintext key, so use that to determine the buffer size.
-            Debug.Assert(cbEncryptedData >= inputCount);
+            Debug.Assert(cbEncryptedData >= input.Length);
             var encryptedData = new byte[cbEncryptedData];
-            Buffer.BlockCopy(input, inputOffset, encryptedData, 0, inputCount);
+            input.CopyTo(encryptedData);
 
             // Encrypt for real - the last parameter is the total size of the in/out buffer, while the second to last
             // parameter specifies the size of the plaintext to encrypt.
-            int encryptedDataLength = inputCount;
+            int encryptedDataLength = input.Length;
             if (!Interop.Advapi32.CryptEncrypt(hKey, SafeHashHandle.InvalidHandle, isFinal, 0, encryptedData, ref encryptedDataLength, cbEncryptedData))
             {
                 throw GetErrorCode().ToCryptographicException();
             }
-            Debug.Assert(encryptedDataLength == cbEncryptedData);
-
-            if (isFinal)
-            {
-                Debug.Assert(outputCount == inputCount);
-            }
-            else
-            {
-                Debug.Assert(outputCount >= encryptedDataLength);
-                outputCount = encryptedDataLength;
-            }
 
-            // If isFinal, padding was added so ignore it by using outputCount as size
-            Buffer.BlockCopy(encryptedData, 0, output, outputOffset, outputCount);
+            // If isFinal, padding was added so ignore it by using original input length as size
+            int outputCount = isFinal ? input.Length : encryptedDataLength;
+            Debug.Assert(encryptedDataLength == cbEncryptedData);
+            Debug.Assert(outputCount <= output.Length);
 
+            encryptedData.AsSpan(0, outputCount).CopyTo(output);
             return outputCount;
         }
 
         internal static int DecryptData(
             SafeKeyHandle hKey,
-            byte[] input,
-            int inputOffset,
-            int inputCount,
-            byte[] output,
-            int outputOffset,
-            int outputCount)
+            ReadOnlySpan<byte> input,
+            Span<byte> output)
         {
             VerifyValidHandle(hKey);
-            Debug.Assert(input != null);
-            Debug.Assert(inputOffset >= 0);
-            Debug.Assert(inputCount >= 0);
-            Debug.Assert(inputCount <= input.Length - inputOffset);
-            Debug.Assert(output != null);
-            Debug.Assert(outputOffset >= 0);
-            Debug.Assert(outputCount >= 0);
-            Debug.Assert(outputCount <= output.Length - outputOffset);
-            Debug.Assert((inputCount % 8) == 0);
-
-            byte[] dataTobeDecrypted = new byte[inputCount];
-            Buffer.BlockCopy(input, inputOffset, dataTobeDecrypted, 0, inputCount);
-
-            int decryptedDataLength = inputCount;
+            Debug.Assert((input.Length % 8) == 0);
+
+            byte[] dataToBeDecrypted = new byte[input.Length];
+            input.CopyTo(dataToBeDecrypted);
+
+            int decryptedDataLength = input.Length;
+
             // Always call decryption with false (not final); deal with padding manually
-            if (!Interop.Advapi32.CryptDecrypt(hKey, SafeHashHandle.InvalidHandle, false, 0, dataTobeDecrypted, ref decryptedDataLength))
+            if (!Interop.Advapi32.CryptDecrypt(hKey, SafeHashHandle.InvalidHandle, false, 0, dataToBeDecrypted, ref decryptedDataLength))
             {
                 throw GetErrorCode().ToCryptographicException();
             }
 
-            Buffer.BlockCopy(dataTobeDecrypted, 0, output, outputOffset, decryptedDataLength);
-
+            dataToBeDecrypted.AsSpan(0, decryptedDataLength).CopyTo(output);
             return decryptedDataLength;
         }