|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | 3 |
|
4 | 4 | using System; |
| 5 | +using System.Buffers.Binary; |
| 6 | +using System.Buffers; |
5 | 7 | using System.Collections.Generic; |
6 | 8 | using System.Diagnostics; |
7 | 9 | using System.Diagnostics.CodeAnalysis; |
|
14 | 16 | using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; |
15 | 17 | using Microsoft.AspNetCore.Shared; |
16 | 18 | using Microsoft.Extensions.Logging; |
| 19 | +using System.Buffers.Text; |
| 20 | +using Microsoft.AspNetCore.DataProtection.Internal; |
17 | 21 |
|
18 | 22 | namespace Microsoft.AspNetCore.DataProtection.KeyManagement; |
19 | 23 |
|
@@ -313,39 +317,13 @@ private static void WriteBigEndianInteger(byte* ptr, uint value) |
313 | 317 | ptr[3] = (byte)(value); |
314 | 318 | } |
315 | 319 |
|
316 | | - private struct AdditionalAuthenticatedDataTemplate |
| 320 | + internal struct AdditionalAuthenticatedDataTemplate |
317 | 321 | { |
318 | 322 | private byte[] _aadTemplate; |
319 | 323 |
|
320 | | - public AdditionalAuthenticatedDataTemplate(IEnumerable<string> purposes) |
| 324 | + public AdditionalAuthenticatedDataTemplate(string[] purposes) |
321 | 325 | { |
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(); |
| 326 | + _aadTemplate = BuildAadTemplateBytes(purposes); |
349 | 327 | } |
350 | 328 |
|
351 | 329 | public byte[] GetAadForKey(Guid keyId, bool isProtecting) |
@@ -381,19 +359,57 @@ public byte[] GetAadForKey(Guid keyId, bool isProtecting) |
381 | 359 | } |
382 | 360 | } |
383 | 361 |
|
384 | | - private sealed class PurposeBinaryWriter : BinaryWriter |
| 362 | + internal static byte[] BuildAadTemplateBytes(string[] purposes) |
385 | 363 | { |
386 | | - public PurposeBinaryWriter(MemoryStream stream) : base(stream, EncodingUtil.SecureUtf8Encoding, leaveOpen: true) { } |
| 364 | + // additionalAuthenticatedData := { magicHeader (32-bit) || keyId || purposeCount (32-bit) || (purpose)* } |
| 365 | + // purpose := { utf8ByteCount (7-bit encoded) || utf8Text } |
| 366 | + |
| 367 | + var keySize = sizeof(Guid); |
| 368 | + int totalPurposeLen = 4 + keySize + 4; |
387 | 369 |
|
388 | | - // Writes a big-endian 32-bit integer to the underlying stream. |
389 | | - public void WriteBigEndian(uint value) |
| 370 | + int[]? lease = null; |
| 371 | + var targetLength = purposes.Length; |
| 372 | + Span<int> purposeLengthsPool = targetLength <= 32 ? stackalloc int[targetLength] : (lease = ArrayPool<int>.Shared.Rent(targetLength)).AsSpan(0, targetLength); |
| 373 | + for (int i = 0; i < targetLength; i++) |
390 | 374 | { |
391 | | - var outStream = BaseStream; // property accessor also performs a flush |
392 | | - outStream.WriteByte((byte)(value >> 24)); |
393 | | - outStream.WriteByte((byte)(value >> 16)); |
394 | | - outStream.WriteByte((byte)(value >> 8)); |
395 | | - outStream.WriteByte((byte)(value)); |
| 375 | + string purpose = purposes[i]; |
| 376 | + |
| 377 | + int purposeLength = EncodingUtil.SecureUtf8Encoding.GetByteCount(purpose); |
| 378 | + purposeLengthsPool[i] = purposeLength; |
| 379 | + |
| 380 | + var encoded7BitUIntLength = purposeLength.Measure7BitEncodedUIntLength(); |
| 381 | + totalPurposeLen += purposeLength /* length of actual string */ + encoded7BitUIntLength /* length of 'string length' 7-bit encoded int */; |
396 | 382 | } |
| 383 | + |
| 384 | + byte[] targetArr = new byte[totalPurposeLen]; |
| 385 | + var targetSpan = targetArr.AsSpan(); |
| 386 | + |
| 387 | + // index 0: magic header |
| 388 | + BinaryPrimitives.WriteUInt32BigEndian(targetSpan.Slice(0), MAGIC_HEADER_V0); |
| 389 | + // index 4: key (skipped for now, will be populated in `GetAadForKey()`) |
| 390 | + // index 4 + keySize: purposeCount |
| 391 | + BinaryPrimitives.WriteInt32BigEndian(targetSpan.Slice(4 + keySize), targetLength); |
| 392 | + |
| 393 | + int index = 4 /* MAGIC_HEADER_V0 */ + keySize + 4 /* purposeLength */; // starting from first purpose |
| 394 | + for (int i = 0; i < targetLength; i++) |
| 395 | + { |
| 396 | + string purpose = purposes[i]; |
| 397 | + |
| 398 | + // writing `utf8ByteCount (7-bit encoded integer)` |
| 399 | + // we have already calculated the lengths of the purpose strings, so just get it from the pool |
| 400 | + index += targetSpan.Slice(index).Write7BitEncodedInt(purposeLengthsPool[i]); |
| 401 | + |
| 402 | + // write the utf8text for the purpose |
| 403 | + index += EncodingUtil.SecureUtf8Encoding.GetBytes(purpose, charIndex: 0, charCount: purpose.Length, bytes: targetArr, byteIndex: index); |
| 404 | + } |
| 405 | + |
| 406 | + if (lease is not null) |
| 407 | + { |
| 408 | + ArrayPool<int>.Shared.Return(lease); |
| 409 | + } |
| 410 | + Debug.Assert(index == targetArr.Length); |
| 411 | + |
| 412 | + return targetArr; |
397 | 413 | } |
398 | 414 | } |
399 | 415 |
|
|
0 commit comments