Skip to content

Commit 2524a15

Browse files
committed
correctHash as rented
1 parent 87d118b commit 2524a15

File tree

3 files changed

+72
-7
lines changed

3 files changed

+72
-7
lines changed

src/DataProtection/Cryptography.Internal/src/CryptoUtil.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ public static bool TimeConstantBuffersAreEqual(byte* bufA, byte* bufB, uint coun
9191
#endif
9292
}
9393

94+
#if NET10_0_OR_GREATER
95+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
96+
public static bool TimeConstantBuffersAreEqual(ReadOnlySpan<byte> bufA, ReadOnlySpan<byte> bufB)
97+
{
98+
// Technically this is an early exit scenario, but it means that the caller did something bizarre.
99+
// An error at the call site isn't usable for timing attacks.
100+
Assert(bufA.Length == bufB.Length, "bufA.Length == bufB.Length");
101+
102+
unsafe
103+
{
104+
return CryptographicOperations.FixedTimeEquals(bufA, bufB);
105+
}
106+
}
107+
#endif
108+
94109
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
95110
public static bool TimeConstantBuffersAreEqual(byte[] bufA, int offsetA, int countA, byte[] bufB, int offsetB, int countB)
96111
{
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace Microsoft.AspNetCore.DataProtection.Internal;
12+
internal static class DataProtectionPool
13+
{
14+
private static readonly ArrayPool<byte> _pool = ArrayPool<byte>.Create();
15+
16+
public static byte[] Rent(int length) => _pool.Rent(length);
17+
public static void Return(byte[] array, bool clearArray = false) => _pool.Return(array, clearArray);
18+
}

src/DataProtection/DataProtection/src/Managed/ManagedAuthenticatedEncryptor.cs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Security.Cryptography;
99
using Microsoft.AspNetCore.Cryptography;
1010
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
11+
using Microsoft.AspNetCore.DataProtection.Internal;
1112
using Microsoft.AspNetCore.DataProtection.SP800_108;
1213

1314
namespace Microsoft.AspNetCore.DataProtection.Managed;
@@ -195,8 +196,10 @@ public byte[] Decrypt(ArraySegment<byte> protectedPayload, ArraySegment<byte> ad
195196
ReadOnlySpan<byte> keyModifier = protectedPayload.Array!.AsSpan(keyModifierOffset, ivOffset - keyModifierOffset);
196197

197198
// Step 2: Decrypt the KDK and use it to restore the original encryption and MAC keys.
198-
// We pin all unencrypted keys to limit their exposure via GC relocation.
199199

200+
// The best optimization is to stackalloc. If the size is too big, we would want to rent from the pool,
201+
// but we can't due to the HashAlgorithm, ValidationAlgorithm and SymmetricAlgorithm requiring a byte[] instead of a Span<byte>
202+
// in the constructor / Key property.
200203
var decryptedKdk = new byte[_keyDerivationKey.Length];
201204
var decryptionSubkey = new byte[_symmetricAlgorithmSubkeyLengthInBytes];
202205
var validationSubkey = new byte[_validationAlgorithmSubkeyLengthInBytes];
@@ -219,16 +222,44 @@ public byte[] Decrypt(ArraySegment<byte> protectedPayload, ArraySegment<byte> ad
219222

220223
// Step 3: Calculate the correct MAC for this payload.
221224
// correctHash := MAC(IV || ciphertext)
222-
byte[] correctHash;
225+
checked
226+
{
227+
eofOffset = protectedPayload.Offset + protectedPayload.Count;
228+
macOffset = eofOffset - _validationAlgorithmDigestLengthInBytes;
229+
}
230+
#if NET10_0_OR_GREATER
231+
using var hashAlgorithm = CreateValidationAlgorithm(validationSubkey);
232+
var hashSize = hashAlgorithm.GetDigestSizeInBytes();
233+
234+
byte[]? correctHashLease = null;
235+
Span<byte> correctHash = hashSize <= 128
236+
? stackalloc byte[hashSize]
237+
: (correctHashLease = DataProtectionPool.Rent(hashSize)).AsSpan(0, hashSize);
238+
try
239+
{
240+
var hashSource = protectedPayload.Array!.AsSpan(ivOffset, macOffset - ivOffset);
241+
hashAlgorithm.TryComputeHash(hashSource, correctHash, out _);
223242

224-
using (var hashAlgorithm = CreateValidationAlgorithm(validationSubkey))
243+
// Step 4: Validate the MAC provided as part of the payload.
244+
var payloadMacPart = protectedPayload.Array!.AsSpan(macOffset, eofOffset - macOffset);
245+
if (!CryptoUtil.TimeConstantBuffersAreEqual(correctHash, payloadMacPart))
246+
{
247+
throw Error.CryptCommon_PayloadInvalid(); // integrity check failure
248+
}
249+
}
250+
finally
225251
{
226-
checked
252+
if (correctHashLease is not null)
227253
{
228-
eofOffset = protectedPayload.Offset + protectedPayload.Count;
229-
macOffset = eofOffset - _validationAlgorithmDigestLengthInBytes;
254+
// it was not cleaned in previous implementation (var correctHash = new byte[])
255+
DataProtectionPool.Return(correctHashLease, clearArray: false);
230256
}
257+
}
258+
#else
259+
byte[] correctHash;
231260

261+
using (var hashAlgorithm = CreateValidationAlgorithm(validationSubkey))
262+
{
232263
correctHash = hashAlgorithm.ComputeHash(protectedPayload.Array!, ivOffset, macOffset - ivOffset);
233264
}
234265

@@ -237,12 +268,13 @@ public byte[] Decrypt(ArraySegment<byte> protectedPayload, ArraySegment<byte> ad
237268
{
238269
throw Error.CryptCommon_PayloadInvalid(); // integrity check failure
239270
}
271+
#endif
240272

241273
// Step 5: Decipher the ciphertext and return it to the caller.
242274
#if NET10_0_OR_GREATER
243275
using var symmetricAlgorithm = CreateSymmetricAlgorithm(key: decryptionSubkey);
244276

245-
// note: here protectedPayload.Array is taken without an offset (cant use AsSpan() on ArraySegment)
277+
// note: here protectedPayload.Array is taken without an offset (can't use AsSpan() on ArraySegment)
246278
var ciphertext = protectedPayload.Array.AsSpan(ciphertextOffset, macOffset - ciphertextOffset);
247279
var iv = protectedPayload.Array.AsSpan(ivOffset, _symmetricAlgorithmBlockSizeInBytes);
248280

0 commit comments

Comments
 (0)