Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit daae3e5

Browse files
committed
Fix output buffer assumptions in CSP symmetric transforms.
The CAPI DecryptData and EncryptData methods made assumptions about the calling patterns, in particular that the output buffer would be perfectly sized for the desired output. This change adds tests which violate those assumptions, but work on netfx and with the corefx CNG implementation. Then follows up on making the tests pass. NetFX idiosyncrasies: AesCryptoServiceProvider.CreateDecryptor on netfx guards against the key not being specified, but no other Aes class does. TripleDESCryptoServiceProvider on netfx doesn't check if the output buffer is too small, so the ArgumentOutOfRangeException ends up being a CryptographicException instead.
1 parent fe0e443 commit daae3e5

File tree

6 files changed

+415
-3
lines changed

6 files changed

+415
-3
lines changed

src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Text;
78
using Test.Cryptography;
89
using Xunit;
910

@@ -629,5 +630,85 @@ private static byte[] AesDecryptDirectKey(Aes aes, byte[] key, byte[] iv, byte[]
629630
return output.ToArray();
630631
}
631632
}
633+
634+
[Theory]
635+
[InlineData(true)]
636+
[InlineData(false)]
637+
public static void EncryptWithLargeOutputBuffer(bool blockAlignedOutput)
638+
{
639+
using (Aes alg = AesFactory.Create())
640+
using (ICryptoTransform xform = alg.CreateEncryptor())
641+
{
642+
// 8 blocks, plus maybe three bytes
643+
int outputPadding = blockAlignedOutput ? 0 : 3;
644+
byte[] output = new byte[alg.BlockSize + outputPadding];
645+
// 2 blocks of 0x00
646+
byte[] input = new byte[alg.BlockSize / 4];
647+
int outputOffset = 0;
648+
649+
outputOffset += xform.TransformBlock(input, 0, input.Length, output, outputOffset);
650+
byte[] overflow = xform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
651+
Buffer.BlockCopy(overflow, 0, output, outputOffset, overflow.Length);
652+
outputOffset += overflow.Length;
653+
654+
Assert.Equal(3 * (alg.BlockSize / 8), outputOffset);
655+
string outputAsHex = output.ByteArrayToHex();
656+
Assert.NotEqual(new string('0', outputOffset * 2), outputAsHex.Substring(0, outputOffset * 2));
657+
Assert.Equal(new string('0', (output.Length - outputOffset) * 2), outputAsHex.Substring(outputOffset * 2));
658+
}
659+
}
660+
661+
[Theory]
662+
[InlineData(true, true)]
663+
[InlineData(true, false)]
664+
[InlineData(false, true)]
665+
[InlineData(false, false)]
666+
public static void TransformWithTooShortOutputBuffer(bool encrypt, bool blockAlignedOutput)
667+
{
668+
// The CreateDecryptor call reads the Key/IV property to initialize them, bypassing an
669+
// uninitialized state protection.
670+
using (Aes alg = AesFactory.Create())
671+
using (ICryptoTransform xform = encrypt ? alg.CreateEncryptor() : alg.CreateDecryptor(alg.Key, alg.IV))
672+
{
673+
// 1 block, plus maybe three bytes
674+
int outputPadding = blockAlignedOutput ? 0 : 3;
675+
byte[] output = new byte[alg.BlockSize / 8 + outputPadding];
676+
// 3 blocks of 0x00
677+
byte[] input = new byte[3 * (alg.BlockSize / 8)];
678+
679+
Assert.Throws<ArgumentOutOfRangeException>(
680+
() => xform.TransformBlock(input, 0, input.Length, output, 0));
681+
682+
Assert.Equal(new byte[output.Length], output);
683+
}
684+
}
685+
686+
[Theory]
687+
[InlineData(true)]
688+
[InlineData(false)]
689+
public static void MultipleBlockDecryptTransform(bool blockAlignedOutput)
690+
{
691+
const string ExpectedOutput = "This is a 128-bit block test";
692+
693+
int outputPadding = blockAlignedOutput ? 0 : 3;
694+
byte[] key = "0123456789ABCDEFFEDCBA9876543210".HexToByteArray();
695+
byte[] iv = "0123456789ABCDEF0123456789ABCDEF".HexToByteArray();
696+
byte[] outputBytes = new byte[iv.Length * 2 + outputPadding];
697+
byte[] input = "D1BF87C650FCD10B758445BE0E0A99D14652480DF53423A8B727D30C8C010EDE".HexToByteArray();
698+
int outputOffset = 0;
699+
700+
using (Aes alg = AesFactory.Create())
701+
using (ICryptoTransform xform = alg.CreateDecryptor(key, iv))
702+
{
703+
Assert.Equal(2 * alg.BlockSize, (outputBytes.Length - outputPadding) * 8);
704+
outputOffset += xform.TransformBlock(input, 0, input.Length, outputBytes, outputOffset);
705+
byte[] overflow = xform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
706+
Buffer.BlockCopy(overflow, 0, outputBytes, outputOffset, overflow.Length);
707+
outputOffset += overflow.Length;
708+
}
709+
710+
string decrypted = Encoding.ASCII.GetString(outputBytes, 0, outputOffset);
711+
Assert.Equal(ExpectedOutput, decrypted);
712+
}
632713
}
633714
}

src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,83 @@ public static void DesExplicitEncryptorDecryptor_NoIV()
225225
}
226226
}
227227
}
228+
229+
[Theory]
230+
[InlineData(true)]
231+
[InlineData(false)]
232+
public static void EncryptWithLargeOutputBuffer(bool blockAlignedOutput)
233+
{
234+
using (DES alg = DESFactory.Create())
235+
using (ICryptoTransform xform = alg.CreateEncryptor())
236+
{
237+
// 8 blocks, plus maybe three bytes
238+
int outputPadding = blockAlignedOutput ? 0 : 3;
239+
byte[] output = new byte[alg.BlockSize + outputPadding];
240+
// 2 blocks of 0x00
241+
byte[] input = new byte[alg.BlockSize / 4];
242+
int outputOffset = 0;
243+
244+
outputOffset += xform.TransformBlock(input, 0, input.Length, output, outputOffset);
245+
byte[] overflow = xform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
246+
Buffer.BlockCopy(overflow, 0, output, outputOffset, overflow.Length);
247+
outputOffset += overflow.Length;
248+
249+
Assert.Equal(3 * (alg.BlockSize / 8), outputOffset);
250+
string outputAsHex = output.ByteArrayToHex();
251+
Assert.NotEqual(new string('0', outputOffset * 2), outputAsHex.Substring(0, outputOffset * 2));
252+
Assert.Equal(new string('0', (output.Length - outputOffset) * 2), outputAsHex.Substring(outputOffset * 2));
253+
}
254+
}
255+
256+
[Theory]
257+
[InlineData(true, true)]
258+
[InlineData(true, false)]
259+
[InlineData(false, true)]
260+
[InlineData(false, false)]
261+
public static void TransformWithTooShortOutputBuffer(bool encrypt, bool blockAlignedOutput)
262+
{
263+
using (DES alg = DESFactory.Create())
264+
using (ICryptoTransform xform = encrypt ? alg.CreateEncryptor() : alg.CreateDecryptor())
265+
{
266+
// 1 block, plus maybe three bytes
267+
int outputPadding = blockAlignedOutput ? 0 : 3;
268+
byte[] output = new byte[alg.BlockSize / 8 + outputPadding];
269+
// 3 blocks of 0x00
270+
byte[] input = new byte[3 * (alg.BlockSize / 8)];
271+
272+
Assert.Throws<ArgumentOutOfRangeException>(
273+
() => xform.TransformBlock(input, 0, input.Length, output, 0));
274+
275+
Assert.Equal(new byte[output.Length], output);
276+
}
277+
}
278+
279+
[Theory]
280+
[InlineData(true)]
281+
[InlineData(false)]
282+
public static void MultipleBlockDecryptTransform(bool blockAlignedOutput)
283+
{
284+
const string ExpectedOutput = "This is a test";
285+
286+
int outputPadding = blockAlignedOutput ? 0 : 3;
287+
byte[] key = "87FF0737F868378F".HexToByteArray();
288+
byte[] iv = "0123456789ABCDEF".HexToByteArray();
289+
byte[] outputBytes = new byte[iv.Length * 2 + outputPadding];
290+
byte[] input = "CB67F70BA8B50EED2C0691298988865F".HexToByteArray();
291+
int outputOffset = 0;
292+
293+
using (DES alg = DESFactory.Create())
294+
using (ICryptoTransform xform = alg.CreateDecryptor(key, iv))
295+
{
296+
Assert.Equal(2 * alg.BlockSize, (outputBytes.Length - outputPadding) * 8);
297+
outputOffset += xform.TransformBlock(input, 0, input.Length, outputBytes, outputOffset);
298+
byte[] overflow = xform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
299+
Buffer.BlockCopy(overflow, 0, outputBytes, outputOffset, overflow.Length);
300+
outputOffset += overflow.Length;
301+
}
302+
303+
string decrypted = Encoding.ASCII.GetString(outputBytes, 0, outputOffset);
304+
Assert.Equal(ExpectedOutput, decrypted);
305+
}
228306
}
229307
}

src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,5 +241,83 @@ public static void RC2ExplicitEncryptorDecryptor_NoIV()
241241
}
242242
}
243243
}
244+
245+
[Theory]
246+
[InlineData(true)]
247+
[InlineData(false)]
248+
public static void EncryptWithLargeOutputBuffer(bool blockAlignedOutput)
249+
{
250+
using (RC2 alg = RC2Factory.Create())
251+
using (ICryptoTransform xform = alg.CreateEncryptor())
252+
{
253+
// 8 blocks, plus maybe three bytes
254+
int outputPadding = blockAlignedOutput ? 0 : 3;
255+
byte[] output = new byte[alg.BlockSize + outputPadding];
256+
// 2 blocks of 0x00
257+
byte[] input = new byte[alg.BlockSize / 4];
258+
int outputOffset = 0;
259+
260+
outputOffset += xform.TransformBlock(input, 0, input.Length, output, outputOffset);
261+
byte[] overflow = xform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
262+
Buffer.BlockCopy(overflow, 0, output, outputOffset, overflow.Length);
263+
outputOffset += overflow.Length;
264+
265+
Assert.Equal(3 * (alg.BlockSize / 8), outputOffset);
266+
string outputAsHex = output.ByteArrayToHex();
267+
Assert.NotEqual(new string('0', outputOffset * 2), outputAsHex.Substring(0, outputOffset * 2));
268+
Assert.Equal(new string('0', (output.Length - outputOffset) * 2), outputAsHex.Substring(outputOffset * 2));
269+
}
270+
}
271+
272+
[Theory]
273+
[InlineData(true, true)]
274+
[InlineData(true, false)]
275+
[InlineData(false, true)]
276+
[InlineData(false, false)]
277+
public static void TransformWithTooShortOutputBuffer(bool encrypt, bool blockAlignedOutput)
278+
{
279+
using (RC2 alg = RC2Factory.Create())
280+
using (ICryptoTransform xform = encrypt ? alg.CreateEncryptor() : alg.CreateDecryptor())
281+
{
282+
// 1 block, plus maybe three bytes
283+
int outputPadding = blockAlignedOutput ? 0 : 3;
284+
byte[] output = new byte[alg.BlockSize / 8 + outputPadding];
285+
// 3 blocks of 0x00
286+
byte[] input = new byte[3 * (alg.BlockSize / 8)];
287+
288+
Assert.Throws<ArgumentOutOfRangeException>(
289+
() => xform.TransformBlock(input, 0, input.Length, output, 0));
290+
291+
Assert.Equal(new byte[output.Length], output);
292+
}
293+
}
294+
295+
[Theory]
296+
[InlineData(true)]
297+
[InlineData(false)]
298+
public static void MultipleBlockDecryptTransform(bool blockAlignedOutput)
299+
{
300+
const string ExpectedOutput = "This is a test";
301+
302+
int outputPadding = blockAlignedOutput ? 0 : 3;
303+
byte[] key = "0123456789ABCDEF".HexToByteArray();
304+
byte[] iv = "0123456789ABCDEF".HexToByteArray();
305+
byte[] outputBytes = new byte[iv.Length * 2 + outputPadding];
306+
byte[] input = "DB5400368C7E67FF5F9E1FA99641EB69".HexToByteArray();
307+
int outputOffset = 0;
308+
309+
using (RC2 alg = RC2Factory.Create())
310+
using (ICryptoTransform xform = alg.CreateDecryptor(key, iv))
311+
{
312+
Assert.Equal(2 * alg.BlockSize, (outputBytes.Length - outputPadding) * 8);
313+
outputOffset += xform.TransformBlock(input, 0, input.Length, outputBytes, outputOffset);
314+
byte[] overflow = xform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
315+
Buffer.BlockCopy(overflow, 0, outputBytes, outputOffset, overflow.Length);
316+
outputOffset += overflow.Length;
317+
}
318+
319+
string decrypted = Encoding.ASCII.GetString(outputBytes, 0, outputOffset);
320+
Assert.Equal(ExpectedOutput, decrypted);
321+
}
244322
}
245323
}

src/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Text;
56
using Test.Cryptography;
67
using Xunit;
78

@@ -288,5 +289,93 @@ public static void TripleDESRoundTripPKCS7CBC(int keySize, string expectedCipher
288289
Assert.Equal<byte>(expectedDecrypted, decrypted);
289290
}
290291
}
292+
293+
[Theory]
294+
[InlineData(true)]
295+
[InlineData(false)]
296+
public static void EncryptWithLargeOutputBuffer(bool blockAlignedOutput)
297+
{
298+
using (TripleDES alg = TripleDESFactory.Create())
299+
using (ICryptoTransform xform = alg.CreateEncryptor())
300+
{
301+
// 8 blocks, plus maybe three bytes
302+
int outputPadding = blockAlignedOutput ? 0 : 3;
303+
byte[] output = new byte[alg.BlockSize + outputPadding];
304+
// 2 blocks of 0x00
305+
byte[] input = new byte[alg.BlockSize / 4];
306+
int outputOffset = 0;
307+
308+
outputOffset += xform.TransformBlock(input, 0, input.Length, output, outputOffset);
309+
byte[] overflow = xform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
310+
Buffer.BlockCopy(overflow, 0, output, outputOffset, overflow.Length);
311+
outputOffset += overflow.Length;
312+
313+
Assert.Equal(3 * (alg.BlockSize / 8), outputOffset);
314+
string outputAsHex = output.ByteArrayToHex();
315+
Assert.NotEqual(new string('0', outputOffset * 2), outputAsHex.Substring(0, outputOffset * 2));
316+
Assert.Equal(new string('0', (output.Length - outputOffset) * 2), outputAsHex.Substring(outputOffset * 2));
317+
}
318+
}
319+
320+
[Theory]
321+
[InlineData(true, true)]
322+
[InlineData(true, false)]
323+
[InlineData(false, true)]
324+
[InlineData(false, false)]
325+
public static void TransformWithTooShortOutputBuffer(bool encrypt, bool blockAlignedOutput)
326+
{
327+
using (TripleDES alg = TripleDESFactory.Create())
328+
using (ICryptoTransform xform = encrypt ? alg.CreateEncryptor() : alg.CreateDecryptor())
329+
{
330+
// 1 block, plus maybe three bytes
331+
int outputPadding = blockAlignedOutput ? 0 : 3;
332+
byte[] output = new byte[alg.BlockSize / 8 + outputPadding];
333+
// 3 blocks of 0x00
334+
byte[] input = new byte[3 * (alg.BlockSize / 8)];
335+
336+
Type exceptionType = typeof(ArgumentOutOfRangeException);
337+
338+
// TripleDESCryptoServiceProvider doesn't throw the ArgumentOutOfRangeException,
339+
// giving a CryptographicException when CAPI reports the destination too small.
340+
if (PlatformDetection.IsFullFramework)
341+
{
342+
exceptionType = typeof(CryptographicException);
343+
}
344+
345+
Assert.Throws(
346+
exceptionType,
347+
() => xform.TransformBlock(input, 0, input.Length, output, 0));
348+
349+
Assert.Equal(new byte[output.Length], output);
350+
}
351+
}
352+
353+
[Theory]
354+
[InlineData(true)]
355+
[InlineData(false)]
356+
public static void MultipleBlockDecryptTransform(bool blockAlignedOutput)
357+
{
358+
const string ExpectedOutput = "This is a test";
359+
360+
int outputPadding = blockAlignedOutput ? 0 : 3;
361+
byte[] key = "0123456789ABCDEFFEDCBA9876543210ABCDEF0123456789".HexToByteArray();
362+
byte[] iv = "0123456789ABCDEF".HexToByteArray();
363+
byte[] outputBytes = new byte[iv.Length * 2 + outputPadding];
364+
byte[] input = "A61C8F1D393202E1E3C71DCEAB9B08DB".HexToByteArray();
365+
int outputOffset = 0;
366+
367+
using (TripleDES alg = TripleDESFactory.Create())
368+
using (ICryptoTransform xform = alg.CreateDecryptor(key, iv))
369+
{
370+
Assert.Equal(2 * alg.BlockSize, (outputBytes.Length - outputPadding) * 8);
371+
outputOffset += xform.TransformBlock(input, 0, input.Length, outputBytes, outputOffset);
372+
byte[] overflow = xform.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
373+
Buffer.BlockCopy(overflow, 0, outputBytes, outputOffset, overflow.Length);
374+
outputOffset += overflow.Length;
375+
}
376+
377+
string decrypted = Encoding.ASCII.GetString(outputBytes, 0, outputOffset);
378+
Assert.Equal(ExpectedOutput, decrypted);
379+
}
291380
}
292381
}

0 commit comments

Comments
 (0)