22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System ;
5+ using System . Buffers . Binary ;
6+ using System . Buffers ;
57using System . Collections . Generic ;
68using System . Diagnostics ;
79using System . Diagnostics . CodeAnalysis ;
1416using Microsoft . AspNetCore . DataProtection . KeyManagement . Internal ;
1517using Microsoft . AspNetCore . Shared ;
1618using Microsoft . Extensions . Logging ;
19+ using System . Buffers . Text ;
1720
1821namespace 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