Skip to content

Commit efd4f79

Browse files
committed
implement timelimitedDataProtector
1 parent 8f5b762 commit efd4f79

File tree

2 files changed

+35
-19
lines changed

2 files changed

+35
-19
lines changed

src/DataProtection/Extensions/src/DataProtectionAdvancedExtensions.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,8 @@ public byte[] Unprotect(byte[] protectedData)
128128
}
129129

130130
#if NET10_0_OR_GREATER
131-
public int GetProtectedSize(ReadOnlySpan<byte> plainText)
132-
{
133-
return _innerProtector.GetProtectedSize(plainText);
134-
}
135-
136-
public bool TryProtect(ReadOnlySpan<byte> plainText, Span<byte> destination, out int bytesWritten)
137-
{
138-
throw new NotImplementedException();
139-
}
131+
public int GetProtectedSize(ReadOnlySpan<byte> plainText) => _innerProtector.GetProtectedSize(plainText);
132+
public bool TryProtect(ReadOnlySpan<byte> plainText, Span<byte> destination, out int bytesWritten) => _innerProtector.TryProtect(plainText, destination, out bytesWritten);
140133
#endif
141134
}
142135
}

src/DataProtection/Extensions/src/TimeLimitedDataProtector.cs

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

44
using System;
5+
using System.Buffers;
56
using System.Diagnostics.CodeAnalysis;
7+
using System.Linq;
68
using System.Security.Cryptography;
79
using System.Threading;
810
using 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

Comments
 (0)