| File: SecureDataFormat.cs | Web Access |
| Project: Microsoft.AspNetCore.Authentication.csproj (Microsoft.AspNetCore.Authentication) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Buffers.Text; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.WebUtilities; namespace Microsoft.AspNetCore.Authentication; /// <summary> /// An implementation for <see cref="ISecureDataFormat{TData}"/>. /// </summary> /// <typeparam name="TData"></typeparam> public class SecureDataFormat<TData> : ISecureDataFormat<TData> { private readonly IDataSerializer<TData> _serializer; private readonly IDataProtector _protector; /// <summary> /// Initializes a new instance of <see cref="SecureDataFormat{TData}"/>. /// </summary> /// <param name="serializer">The <see cref="IDataSerializer{TModel}"/>.</param> /// <param name="protector">The <see cref="IDataProtector"/>.</param> public SecureDataFormat(IDataSerializer<TData> serializer, IDataProtector protector) { _serializer = serializer; _protector = protector; } /// <inheritdoc /> public string Protect(TData data) { return Protect(data, purpose: null); } /// <inheritdoc /> public string Protect(TData data, string? purpose) { var userData = _serializer.Serialize(data); var protector = _protector; if (!string.IsNullOrEmpty(purpose)) { protector = protector.CreateProtector(purpose); } if (protector is ISpanDataProtector spanProtector) { return SpanProtect(spanProtector, userData); } var protectedData = protector.Protect(userData); return Base64UrlTextEncoder.Encode(protectedData); } private static string SpanProtect(ISpanDataProtector protector, byte[] userData) { var writer = new RefPooledArrayBufferWriter<byte>(stackalloc byte[256]); try { protector.Protect(userData, ref writer); return WebEncoders.Base64UrlEncode(writer.WrittenSpan); } finally { writer.Dispose(); } } /// <inheritdoc /> public TData? Unprotect(string? protectedText) { return Unprotect(protectedText, purpose: null); } /// <inheritdoc /> public TData? Unprotect(string? protectedText, string? purpose) { try { if (protectedText == null) { return default(TData); } var protector = _protector; if (!string.IsNullOrEmpty(purpose)) { protector = protector.CreateProtector(purpose); } var userData = protector is ISpanDataProtector spanProtector ? SpanUnprotect(spanProtector, protectedText) : Unprotect(protector, protectedText); if (userData == null) { return default(TData); } return _serializer.Deserialize(userData); } catch { // TODO trace exception, but do not leak other information return default(TData); } } private static byte[]? Unprotect(IDataProtector protector, string protectedText) { var protectedData = Base64UrlTextEncoder.Decode(protectedText); if (protectedData is null) { return null; } return protector.Unprotect(protectedData); } private static byte[]? SpanUnprotect(ISpanDataProtector protector, string protectedText) { var protectedBuffer = ArrayPool<byte>.Shared.Rent(Base64Url.GetMaxDecodedLength(protectedText.Length)); try { if (Base64Url.DecodeFromChars(protectedText, protectedBuffer, out _, out var protectedLength) != OperationStatus.Done) { // Base64UrlTextEncoder.Decode also accepts standard base64, so let it handle anything that isn't strict base64url. return Unprotect(protector, protectedText); } Span<byte> stackBuffer = stackalloc byte[256]; var writer = new RefPooledArrayBufferWriter<byte>(stackBuffer); try { protector.Unprotect(protectedBuffer.AsSpan(0, protectedLength), ref writer); return writer.WrittenSpan.ToArray(); } finally { // Dispose clears any rented buffer; the stack buffer can also hold plaintext. writer.Dispose(); stackBuffer.Clear(); } } finally { ArrayPool<byte>.Shared.Return(protectedBuffer); } } }