22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System ;
5+ using System . Buffers ;
56using System . Diagnostics . CodeAnalysis ;
7+ using System . Linq ;
68using System . Security . Cryptography ;
79using System . Threading ;
810using Microsoft . AspNetCore . DataProtection . Extensions ;
@@ -21,6 +23,8 @@ internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
2123 private readonly IDataProtector _innerProtector ;
2224 private IDataProtector ? _innerProtectorWithTimeLimitedPurpose ; // created on-demand
2325
26+ private const int ExpirationTimeHeaderSize = 8 ; // size of the expiration time header in bytes (64-bit UTC tick count)
27+
2428 public TimeLimitedDataProtector ( IDataProtector innerProtector )
2529 {
2630 _innerProtector = innerProtector ;
@@ -50,9 +54,9 @@ public byte[] Protect(byte[] plaintext, DateTimeOffset expiration)
5054 ArgumentNullThrowHelper . ThrowIfNull ( plaintext ) ;
5155
5256 // We prepend the expiration time (as a 64-bit UTC tick count) to the unprotected data.
53- byte [ ] plaintextWithHeader = new byte [ checked ( 8 + plaintext . Length ) ] ;
57+ byte [ ] plaintextWithHeader = new byte [ checked ( ExpirationTimeHeaderSize + plaintext . Length ) ] ;
5458 BitHelpers . WriteUInt64 ( plaintextWithHeader , 0 , ( ulong ) expiration . UtcTicks ) ;
55- Buffer . BlockCopy ( plaintext , 0 , plaintextWithHeader , 8 , plaintext . Length ) ;
59+ Buffer . BlockCopy ( plaintext , 0 , plaintextWithHeader , ExpirationTimeHeaderSize , plaintext . Length ) ;
5660
5761 return GetInnerProtectorWithTimeLimitedPurpose ( ) . Protect ( plaintextWithHeader ) ;
5862 }
@@ -71,7 +75,7 @@ internal byte[] UnprotectCore(byte[] protectedData, DateTimeOffset now, out Date
7175 try
7276 {
7377 byte [ ] plaintextWithHeader = GetInnerProtectorWithTimeLimitedPurpose ( ) . Unprotect ( protectedData ) ;
74- if ( plaintextWithHeader . Length < 8 )
78+ if ( plaintextWithHeader . Length < ExpirationTimeHeaderSize )
7579 {
7680 // header isn't present
7781 throw new CryptographicException ( Resources . TimeLimitedDataProtector_PayloadInvalid ) ;
@@ -88,8 +92,8 @@ internal byte[] UnprotectCore(byte[] protectedData, DateTimeOffset now, out Date
8892 }
8993
9094 // Not expired - split and return payload
91- byte [ ] retVal = new byte [ plaintextWithHeader . Length - 8 ] ;
92- Buffer . BlockCopy ( plaintextWithHeader , 8 , retVal , 0 , retVal . Length ) ;
95+ byte [ ] retVal = new byte [ plaintextWithHeader . Length - ExpirationTimeHeaderSize ] ;
96+ Buffer . BlockCopy ( plaintextWithHeader , ExpirationTimeHeaderSize , retVal , 0 , retVal . Length ) ;
9397 expiration = embeddedExpiration ;
9498 return retVal ;
9599 }
@@ -132,19 +136,38 @@ public int GetProtectedSize(ReadOnlySpan<byte> plainText)
132136 var dataProtector = GetInnerProtectorWithTimeLimitedPurpose ( ) ;
133137 var size = dataProtector . GetProtectedSize ( plainText ) ;
134138
135- // prepended the expiration time as a 64-bit UTC tick count takes 8 bytes;
139+ // prepended the expiration time as a 64-bit UTC tick count takes ExpirationTimeHeaderSize bytes;
136140 // see Protect(byte[] plaintext, DateTimeOffset expiration) for details
137- return size + 8 ;
141+ return size + ExpirationTimeHeaderSize ;
138142 }
139143
140144 public bool TryProtect ( ReadOnlySpan < byte > plaintext , Span < byte > destination , out int bytesWritten )
141145 => TryProtect ( plaintext , destination , DateTimeOffset . MaxValue , out bytesWritten ) ;
142146
143147 public bool TryProtect ( ReadOnlySpan < byte > plaintext , Span < byte > destination , DateTimeOffset expiration , out int bytesWritten )
144148 {
145- // we cant prepend the expiration time as a 64-bit UTC tick count
146- // because input is ReadOnlySpan<byte>
147- throw new NotImplementedException ( ) ;
149+ // we need to prepend the expiration time, so we need to allocate a buffer for the plaintext with header
150+ byte [ ] ? plainTextWithHeader = null ;
151+ try
152+ {
153+ plainTextWithHeader = ArrayPool < byte > . Shared . Rent ( plaintext . Length + ExpirationTimeHeaderSize ) ;
154+ var plainTextWithHeaderSpan = plainTextWithHeader . AsSpan ( ) ;
155+
156+ // We prepend the expiration time (as a 64-bit UTC tick count) to the unprotected data.
157+ BitHelpers . WriteUInt64 ( plainTextWithHeaderSpan , 0 , ( ulong ) expiration . UtcTicks ) ;
158+
159+ // and copy the plaintext into the buffer
160+ plaintext . CopyTo ( plainTextWithHeaderSpan . Slice ( ExpirationTimeHeaderSize ) ) ;
161+
162+ return _innerProtector . TryProtect ( plainTextWithHeaderSpan , destination , out bytesWritten ) ;
163+ }
164+ finally
165+ {
166+ if ( plainTextWithHeader is not null )
167+ {
168+ ArrayPool < byte > . Shared . Return ( plainTextWithHeader ) ;
169+ }
170+ }
148171 }
149172#endif
150173}
0 commit comments