Skip to content

Commit c686038

Browse files
committed
get rid of memoryStream usage
1 parent 3fe0618 commit c686038

File tree

1 file changed

+118
-31
lines changed

1 file changed

+118
-31
lines changed

src/DataProtection/DataProtection/src/KeyManagement/KeyRingBasedDataProtector.cs

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Buffers.Binary;
6+
using System.Buffers;
57
using System.Collections.Generic;
68
using System.Diagnostics;
79
using System.Diagnostics.CodeAnalysis;
@@ -14,6 +16,7 @@
1416
using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
1517
using Microsoft.AspNetCore.Shared;
1618
using Microsoft.Extensions.Logging;
19+
using System.Buffers.Text;
1720

1821
namespace Microsoft.AspNetCore.DataProtection.KeyManagement;
1922

@@ -317,37 +320,6 @@ private struct AdditionalAuthenticatedDataTemplate
317320
{
318321
private byte[] _aadTemplate;
319322

320-
public AdditionalAuthenticatedDataTemplate(IEnumerable<string> purposes)
321-
{
322-
const int MEMORYSTREAM_DEFAULT_CAPACITY = 0x100; // matches MemoryStream.EnsureCapacity
323-
var ms = new MemoryStream(MEMORYSTREAM_DEFAULT_CAPACITY);
324-
325-
// additionalAuthenticatedData := { magicHeader (32-bit) || keyId || purposeCount (32-bit) || (purpose)* }
326-
// purpose := { utf8ByteCount (7-bit encoded) || utf8Text }
327-
328-
using (var writer = new PurposeBinaryWriter(ms))
329-
{
330-
writer.WriteBigEndian(MAGIC_HEADER_V0);
331-
Debug.Assert(ms.Position == sizeof(uint));
332-
var posPurposeCount = writer.Seek(sizeof(Guid), SeekOrigin.Current); // skip over where the key id will be stored; we'll fill it in later
333-
writer.Seek(sizeof(uint), SeekOrigin.Current); // skip over where the purposeCount will be stored; we'll fill it in later
334-
335-
uint purposeCount = 0;
336-
foreach (string purpose in purposes)
337-
{
338-
Debug.Assert(purpose != null);
339-
writer.Write(purpose); // prepends length as a 7-bit encoded integer
340-
purposeCount++;
341-
}
342-
343-
// Once we have written all the purposes, go back and fill in 'purposeCount'
344-
writer.Seek(checked((int)posPurposeCount), SeekOrigin.Begin);
345-
writer.WriteBigEndian(purposeCount);
346-
}
347-
348-
_aadTemplate = ms.ToArray();
349-
}
350-
351323
public byte[] GetAadForKey(Guid keyId, bool isProtecting)
352324
{
353325
// Multiple threads might be trying to read and write the _aadTemplate field
@@ -381,6 +353,120 @@ public byte[] GetAadForKey(Guid keyId, bool isProtecting)
381353
}
382354
}
383355

356+
#if NET10_0_OR_GREATER
357+
public AdditionalAuthenticatedDataTemplate(string[] purposes)
358+
{
359+
// additionalAuthenticatedData := { magicHeader (32-bit) || keyId || purposeCount (32-bit) || (purpose)* }
360+
// purpose := { utf8ByteCount (7-bit encoded) || utf8Text }
361+
362+
var keySize = sizeof(Guid);
363+
int totalPurposeLen = 4 + keySize + 4;
364+
365+
var purposeLengthsPool = ArrayPool<int>.Shared.Rent(purposes.Length);
366+
for (int i = 0; i < purposes.Length; i++)
367+
{
368+
string purpose = purposes[i];
369+
370+
int purposeLength = EncodingUtil.SecureUtf8Encoding.GetByteCount(purpose);
371+
purposeLengthsPool[i] = purposeLength;
372+
373+
var encoded7BitUIntLength = Measure7BitEncodedUIntLength((uint)purposeLength);
374+
totalPurposeLen += purposeLength /* length of actual string */ + encoded7BitUIntLength /* length of 'string length' 7-bit encoded int */;
375+
}
376+
377+
byte[] targetArr = new byte[totalPurposeLen];
378+
var targetSpan = targetArr.AsSpan();
379+
380+
// index 0: magic header
381+
BinaryPrimitives.WriteUInt32BigEndian(targetSpan.Slice(0), MAGIC_HEADER_V0);
382+
// index 4: key (skipped for now, will be populated in `GetAadForKey()`)
383+
// index 4 + keySize: purposeCount
384+
BinaryPrimitives.WriteInt32BigEndian(targetSpan.Slice(4 + keySize), purposes.Length);
385+
386+
int index = 4 + keySize + 4; // starting from first purpose
387+
for (int i = 0; i < purposes.Length; i++)
388+
{
389+
string purpose = purposes[i];
390+
391+
// writing `utf8ByteCount (7-bit encoded integer) || utf8Text`
392+
// we have already calculated the lengths of the purpose strings, so just get it from the pool
393+
index += Write7BitEncodedInt(purposeLengthsPool[i], targetSpan.Slice(index));
394+
index += EncodingUtil.SecureUtf8Encoding.GetBytes(purpose.AsSpan(), targetSpan.Slice(index));
395+
}
396+
397+
ArrayPool<int>.Shared.Return(purposeLengthsPool);
398+
Debug.Assert(index == targetArr.Length);
399+
400+
Console.WriteLine("Original purposes: " + string.Join(";", purposes));
401+
Console.WriteLine("Payload: " + Convert.ToBase64String(targetArr));
402+
_aadTemplate = targetArr;
403+
}
404+
405+
private static int Measure7BitEncodedUIntLength(uint value)
406+
{
407+
return ((31 - System.Numerics.BitOperations.LeadingZeroCount(value | 1)) / 7) + 1;
408+
409+
// does the same as the following code:
410+
// int count = 1;
411+
// while ((value >>= 7) != 0)
412+
// {
413+
// count++;
414+
// }
415+
// return count;
416+
}
417+
418+
private static int Write7BitEncodedInt(int value, Span<byte> target)
419+
{
420+
uint uValue = (uint)value;
421+
422+
// Write out an int 7 bits at a time. The high bit of the byte,
423+
// when on, tells reader to continue reading more bytes.
424+
//
425+
// Using the constants 0x7F and ~0x7F below offers smaller
426+
// codegen than using the constant 0x80.
427+
428+
int index = 0;
429+
while (uValue > 0x7Fu)
430+
{
431+
target[index++] = (byte)(uValue | ~0x7Fu);
432+
uValue >>= 7;
433+
}
434+
435+
target[index++] = (byte)uValue;
436+
return index;
437+
}
438+
#else
439+
public AdditionalAuthenticatedDataTemplate(IEnumerable<string> purposes)
440+
{
441+
const int MEMORYSTREAM_DEFAULT_CAPACITY = 0x100; // matches MemoryStream.EnsureCapacity
442+
var ms = new MemoryStream(MEMORYSTREAM_DEFAULT_CAPACITY);
443+
444+
// additionalAuthenticatedData := { magicHeader (32-bit) || keyId || purposeCount (32-bit) || (purpose)* }
445+
// purpose := { utf8ByteCount (7-bit encoded) || utf8Text }
446+
447+
using (var writer = new PurposeBinaryWriter(ms))
448+
{
449+
writer.WriteBigEndian(MAGIC_HEADER_V0);
450+
Debug.Assert(ms.Position == sizeof(uint));
451+
var posPurposeCount = writer.Seek(sizeof(Guid), SeekOrigin.Current); // skip over where the key id will be stored; we'll fill it in later
452+
writer.Seek(sizeof(uint), SeekOrigin.Current); // skip over where the purposeCount will be stored; we'll fill it in later
453+
454+
uint purposeCount = 0;
455+
foreach (string purpose in purposes)
456+
{
457+
Debug.Assert(purpose != null);
458+
writer.Write(purpose); // prepends length as a 7-bit encoded integer
459+
purposeCount++;
460+
}
461+
462+
// Once we have written all the purposes, go back and fill in 'purposeCount'
463+
writer.Seek(checked((int)posPurposeCount), SeekOrigin.Begin);
464+
writer.WriteBigEndian(purposeCount);
465+
}
466+
467+
_aadTemplate = ms.ToArray();
468+
}
469+
384470
private sealed class PurposeBinaryWriter : BinaryWriter
385471
{
386472
public PurposeBinaryWriter(MemoryStream stream) : base(stream, EncodingUtil.SecureUtf8Encoding, leaveOpen: true) { }
@@ -395,6 +481,7 @@ public void WriteBigEndian(uint value)
395481
outStream.WriteByte((byte)(value));
396482
}
397483
}
484+
#endif
398485
}
399486

400487
private enum UnprotectStatus

0 commit comments

Comments
 (0)