aspnetcore/src/Microsoft.AspNetCore.Antifo.../DefaultAntiforgeryTokenSeri...

146 lines
5.3 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.WebUtilities;
namespace Microsoft.AspNetCore.Antiforgery
{
public class DefaultAntiforgeryTokenSerializer : IAntiforgeryTokenSerializer
{
private static readonly string Purpose = "Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1";
private readonly IDataProtector _cryptoSystem;
private const byte TokenVersion = 0x01;
public DefaultAntiforgeryTokenSerializer(IDataProtectionProvider provider)
{
if (provider == null)
{
throw new ArgumentNullException(nameof(provider));
}
_cryptoSystem = provider.CreateProtector(Purpose);
}
public AntiforgeryToken Deserialize(string serializedToken)
{
Exception innerException = null;
try
{
var tokenBytes = WebEncoders.Base64UrlDecode(serializedToken);
using (var stream = new MemoryStream(_cryptoSystem.Unprotect(tokenBytes)))
{
using (var reader = new BinaryReader(stream))
{
var token = DeserializeImpl(reader);
if (token != null)
{
return token;
}
}
}
}
catch (Exception ex)
{
// swallow all exceptions - homogenize error if something went wrong
innerException = ex;
}
// if we reached this point, something went wrong deserializing
throw new InvalidOperationException(Resources.AntiforgeryToken_DeserializationFailed, innerException);
}
/* The serialized format of the anti-XSRF token is as follows:
* Version: 1 byte integer
* SecurityToken: 16 byte binary blob
* IsCookieToken: 1 byte Boolean
* [if IsCookieToken != true]
* +- IsClaimsBased: 1 byte Boolean
* | [if IsClaimsBased = true]
* | `- ClaimUid: 32 byte binary blob
* | [if IsClaimsBased = false]
* | `- Username: UTF-8 string with 7-bit integer length prefix
* `- AdditionalData: UTF-8 string with 7-bit integer length prefix
*/
private static AntiforgeryToken DeserializeImpl(BinaryReader reader)
{
// we can only consume tokens of the same serialized version that we generate
var embeddedVersion = reader.ReadByte();
if (embeddedVersion != TokenVersion)
{
return null;
}
var deserializedToken = new AntiforgeryToken();
var securityTokenBytes = reader.ReadBytes(AntiforgeryToken.SecurityTokenBitLength / 8);
deserializedToken.SecurityToken =
new BinaryBlob(AntiforgeryToken.SecurityTokenBitLength, securityTokenBytes);
deserializedToken.IsCookieToken = reader.ReadBoolean();
if (!deserializedToken.IsCookieToken)
{
var isClaimsBased = reader.ReadBoolean();
if (isClaimsBased)
{
var claimUidBytes = reader.ReadBytes(AntiforgeryToken.ClaimUidBitLength / 8);
deserializedToken.ClaimUid = new BinaryBlob(AntiforgeryToken.ClaimUidBitLength, claimUidBytes);
}
else
{
deserializedToken.Username = reader.ReadString();
}
deserializedToken.AdditionalData = reader.ReadString();
}
// if there's still unconsumed data in the stream, fail
if (reader.BaseStream.ReadByte() != -1)
{
return null;
}
// success
return deserializedToken;
}
public string Serialize(AntiforgeryToken token)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
using (var stream = new MemoryStream())
{
using (var writer = new BinaryWriter(stream))
{
writer.Write(TokenVersion);
writer.Write(token.SecurityToken.GetData());
writer.Write(token.IsCookieToken);
if (!token.IsCookieToken)
{
if (token.ClaimUid != null)
{
writer.Write(true /* isClaimsBased */);
writer.Write(token.ClaimUid.GetData());
}
else
{
writer.Write(false /* isClaimsBased */);
writer.Write(token.Username);
}
writer.Write(token.AdditionalData);
}
writer.Flush();
return WebEncoders.Base64UrlEncode(_cryptoSystem.Protect(stream.ToArray()));
}
}
}
}
}