Skip to content

Commit 05fdcc3

Browse files
committed
other impls
1 parent 35f7522 commit 05fdcc3

File tree

3 files changed

+85
-81
lines changed

3 files changed

+85
-81
lines changed

src/DataProtection/Extensions/src/DataProtectionAdvancedExtensions.cs

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,10 @@ public static string Unprotect(this ITimeLimitedDataProtector protector, string
9696
return retVal;
9797
}
9898

99-
private sealed class TimeLimitedWrappingProtector : IDataProtector
100-
#if NET10_0_OR_GREATER
101-
, ISpanDataProtector
102-
#endif
99+
private class TimeLimitedWrappingProtector : IDataProtector
103100
{
104101
public DateTimeOffset Expiration;
105-
private readonly ITimeLimitedDataProtector _innerProtector;
102+
protected readonly ITimeLimitedDataProtector _innerProtector;
106103

107104
public TimeLimitedWrappingProtector(ITimeLimitedDataProtector innerProtector)
108105
{
@@ -129,28 +126,24 @@ public byte[] Unprotect(byte[] protectedData)
129126

130127
return _innerProtector.Unprotect(protectedData, out Expiration);
131128
}
129+
}
132130

133-
#if NET10_0_OR_GREATER
134-
public int GetProtectedSize(ReadOnlySpan<byte> plainText)
131+
private class TimeLimitedWrappingSpanProtector : TimeLimitedWrappingProtector, ISpanDataProtector
132+
{
133+
public TimeLimitedWrappingSpanProtector(ITimeLimitedDataProtector innerProtector) : base(innerProtector)
135134
{
136-
if (_innerProtector is ISpanDataProtector optimizedDataProtector)
137-
{
138-
return optimizedDataProtector.TryGetProtectedSize(plainText, out cipherTextLength);
139-
}
135+
}
140136

141-
cipherTextLength = default;
142-
return false;
137+
public int GetProtectedSize(ReadOnlySpan<byte> plainText)
138+
{
139+
var inner = (ISpanDataProtector)_innerProtector;
140+
return inner.GetProtectedSize(plainText);
143141
}
144142

145143
public bool TryProtect(ReadOnlySpan<byte> plainText, Span<byte> destination, out int bytesWritten)
146144
{
147-
if (_innerProtector is ISpanDataProtector optimizedDataProtector)
148-
{
149-
return optimizedDataProtector.TryProtect(plainText, destination, out bytesWritten);
150-
}
151-
152-
throw new NotSupportedException("The inner protector does not support optimized data protection.");
145+
var inner = (ISpanDataProtector)_innerProtector;
146+
return inner.TryProtect(plainText, destination, out bytesWritten);
153147
}
154-
#endif
155148
}
156149
}

src/DataProtection/Extensions/src/TimeLimitedDataProtector.cs

Lines changed: 11 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,14 @@ namespace Microsoft.AspNetCore.DataProtection;
1616
/// Wraps an existing <see cref="IDataProtector"/> and appends a purpose that allows
1717
/// protecting data with a finite lifetime.
1818
/// </summary>
19-
internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
20-
#if NET10_0_OR_GREATER
21-
, ISpanDataProtector
22-
#endif
19+
internal class TimeLimitedDataProtector : ITimeLimitedDataProtector
2320
{
2421
private const string MyPurposeString = "Microsoft.AspNetCore.DataProtection.TimeLimitedDataProtector.v1";
2522

26-
private readonly IDataProtector _innerProtector;
2723
private IDataProtector? _innerProtectorWithTimeLimitedPurpose; // created on-demand
24+
protected readonly IDataProtector _innerProtector;
2825

29-
private const int ExpirationTimeHeaderSize = 8; // size of the expiration time header in bytes (64-bit UTC tick count)
26+
protected const int ExpirationTimeHeaderSize = 8; // size of the expiration time header in bytes (64-bit UTC tick count)
3027

3128
public TimeLimitedDataProtector(IDataProtector innerProtector)
3229
{
@@ -37,10 +34,16 @@ public ITimeLimitedDataProtector CreateProtector(string purpose)
3734
{
3835
ArgumentNullThrowHelper.ThrowIfNull(purpose);
3936

40-
return new TimeLimitedDataProtector(_innerProtector.CreateProtector(purpose));
37+
var protector = _innerProtector.CreateProtector(purpose);
38+
if (protector is ISpanDataProtector spanDataProtector)
39+
{
40+
return new TimeLimitedSpanDataProtector(spanDataProtector);
41+
}
42+
43+
return new TimeLimitedDataProtector(protector);
4144
}
4245

43-
private IDataProtector GetInnerProtectorWithTimeLimitedPurpose()
46+
protected IDataProtector GetInnerProtectorWithTimeLimitedPurpose()
4447
{
4548
// thread-safe lazy init pattern with multi-execution and single publication
4649
var retVal = Volatile.Read(ref _innerProtectorWithTimeLimitedPurpose);
@@ -132,57 +135,4 @@ byte[] IDataProtector.Unprotect(byte[] protectedData)
132135

133136
return Unprotect(protectedData, out _);
134137
}
135-
136-
#if NET10_0_OR_GREATER
137-
public bool TryGetProtectedSize(ReadOnlySpan<byte> plainText, out int cipherTextLength)
138-
{
139-
var dataProtector = GetInnerProtectorWithTimeLimitedPurpose();
140-
if (dataProtector is ISpanDataProtector optimizedDataProtector)
141-
{
142-
var result = optimizedDataProtector.TryGetProtectedSize(plainText, out cipherTextLength);
143-
144-
// prepended the expiration time as a 64-bit UTC tick count takes ExpirationTimeHeaderSize bytes;
145-
// see Protect(byte[] plaintext, DateTimeOffset expiration) for details
146-
cipherTextLength += ExpirationTimeHeaderSize;
147-
return result;
148-
}
149-
150-
cipherTextLength = default;
151-
return false;
152-
}
153-
154-
public bool TryProtect(ReadOnlySpan<byte> plaintext, Span<byte> destination, out int bytesWritten)
155-
=> TryProtect(plaintext, destination, DateTimeOffset.MaxValue, out bytesWritten);
156-
157-
public bool TryProtect(ReadOnlySpan<byte> plaintext, Span<byte> destination, DateTimeOffset expiration, out int bytesWritten)
158-
{
159-
if (_innerProtector is not ISpanDataProtector optimizedDataProtector)
160-
{
161-
throw new NotSupportedException("The inner protector does not support optimized data protection.");
162-
}
163-
164-
// we need to prepend the expiration time, so we need to allocate a buffer for the plaintext with header
165-
byte[]? plainTextWithHeader = null;
166-
try
167-
{
168-
plainTextWithHeader = ArrayPool<byte>.Shared.Rent(plaintext.Length + ExpirationTimeHeaderSize);
169-
var plainTextWithHeaderSpan = plainTextWithHeader.AsSpan(0, plaintext.Length + ExpirationTimeHeaderSize);
170-
171-
// We prepend the expiration time (as a 64-bit UTC tick count) to the unprotected data.
172-
BitHelpers.WriteUInt64(plainTextWithHeaderSpan, 0, (ulong)expiration.UtcTicks);
173-
174-
// and copy the plaintext into the buffer
175-
plaintext.CopyTo(plainTextWithHeaderSpan.Slice(ExpirationTimeHeaderSize));
176-
177-
return optimizedDataProtector.TryProtect(plainTextWithHeaderSpan, destination, out bytesWritten);
178-
}
179-
finally
180-
{
181-
if (plainTextWithHeader is not null)
182-
{
183-
ArrayPool<byte>.Shared.Return(plainTextWithHeader);
184-
}
185-
}
186-
}
187-
#endif
188138
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Linq;
8+
using System.Security.Cryptography;
9+
using System.Threading;
10+
using Microsoft.AspNetCore.DataProtection.Extensions;
11+
using Microsoft.AspNetCore.Shared;
12+
13+
namespace Microsoft.AspNetCore.DataProtection;
14+
15+
/// <summary>
16+
/// Wraps an existing <see cref="IDataProtector"/> and appends a purpose that allows
17+
/// protecting data with a finite lifetime.
18+
/// </summary>
19+
internal sealed class TimeLimitedSpanDataProtector : TimeLimitedDataProtector, ISpanDataProtector
20+
{
21+
public TimeLimitedSpanDataProtector(ISpanDataProtector innerProtector) : base(innerProtector)
22+
{
23+
}
24+
25+
public int GetProtectedSize(ReadOnlySpan<byte> plainText)
26+
{
27+
var dataProtector = (ISpanDataProtector)GetInnerProtectorWithTimeLimitedPurpose();
28+
return dataProtector.GetProtectedSize(plainText) + ExpirationTimeHeaderSize;
29+
}
30+
31+
public bool TryProtect(ReadOnlySpan<byte> plaintext, Span<byte> destination, out int bytesWritten)
32+
=> TryProtect(plaintext, destination, DateTimeOffset.MaxValue, out bytesWritten);
33+
34+
public bool TryProtect(ReadOnlySpan<byte> plaintext, Span<byte> destination, DateTimeOffset expiration, out int bytesWritten)
35+
{
36+
var innerProtector = (ISpanDataProtector)_innerProtector;
37+
38+
// we need to prepend the expiration time, so we need to allocate a buffer for the plaintext with header
39+
byte[]? plainTextWithHeader = null;
40+
try
41+
{
42+
plainTextWithHeader = ArrayPool<byte>.Shared.Rent(plaintext.Length + ExpirationTimeHeaderSize);
43+
var plainTextWithHeaderSpan = plainTextWithHeader.AsSpan(0, plaintext.Length + ExpirationTimeHeaderSize);
44+
45+
// We prepend the expiration time (as a 64-bit UTC tick count) to the unprotected data.
46+
BitHelpers.WriteUInt64(plainTextWithHeaderSpan, 0, (ulong)expiration.UtcTicks);
47+
48+
// and copy the plaintext into the buffer
49+
plaintext.CopyTo(plainTextWithHeaderSpan.Slice(ExpirationTimeHeaderSize));
50+
51+
return innerProtector.TryProtect(plainTextWithHeaderSpan, destination, out bytesWritten);
52+
}
53+
finally
54+
{
55+
if (plainTextWithHeader is not null)
56+
{
57+
ArrayPool<byte>.Shared.Return(plainTextWithHeader);
58+
}
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)