|
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