// Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Claims; using System.Text; using Microsoft.AspNet.Security.DataProtection; using Moq; using Xunit; namespace Microsoft.AspNet.Mvc.Core.Test { public class AntiForgeryTokenSerializerTest { private static readonly Mock _dataProtector = GetDataProtector(); private static readonly BinaryBlob _claimUid = new BinaryBlob(256, new byte[] { 0x6F, 0x16, 0x48, 0xE9, 0x72, 0x49, 0xAA, 0x58, 0x75, 0x40, 0x36, 0xA6, 0x7E, 0x24, 0x8C, 0xF0, 0x44, 0xF0, 0x7E, 0xCF, 0xB0, 0xED, 0x38, 0x75, 0x56, 0xCE, 0x02, 0x9A, 0x4F, 0x9A, 0x40, 0xE0 }); private static readonly BinaryBlob _securityToken = new BinaryBlob(128, new byte[] { 0x70, 0x5E, 0xED, 0xCC, 0x7D, 0x42, 0xF1, 0xD6, 0xB3, 0xB9, 0x8A, 0x59, 0x36, 0x25, 0xBB, 0x4C }); private const byte _salt = 0x05; [Theory] [InlineData( "01" // Version + "705EEDCC7D42F1D6B3B9" // SecurityToken // (WRONG!) Stream ends too early )] [InlineData( "01" // Version + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken + "01" // IsSessionToken + "00" // (WRONG!) Too much data in stream )] [InlineData( "02" // (WRONG! - must be 0x01) Version + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken + "01" // IsSessionToken )] [InlineData( "01" // Version + "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken + "00" // IsSessionToken + "00" // IsClaimsBased + "05" // Username length header + "0000" // (WRONG!) Too little data in stream )] public void Deserialize_BadToken_Throws(string serializedToken) { // Arrange var testSerializer = new AntiForgeryTokenSerializer(_dataProtector.Object); // Act & assert var ex = Assert.Throws(() => testSerializer.Deserialize(serializedToken)); Assert.Equal(@"The anti-forgery token could not be decrypted.", ex.Message); } [Fact] public void Serialize_FieldToken_WithClaimUid_TokenRoundTripSuccessful() { // Arrange var testSerializer = new AntiForgeryTokenSerializer(_dataProtector.Object); //"01" // Version //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken //+ "00" // IsSessionToken //+ "01" // IsClaimsBased //+ "6F1648E97249AA58754036A67E248CF044F07ECFB0ED387556CE029A4F9A40E0" // ClaimUid //+ "05" // AdditionalData length header //+ "E282AC3437"; // AdditionalData ("€47") as UTF8 var token = new AntiForgeryToken() { SecurityToken = _securityToken, IsSessionToken = false, ClaimUid = _claimUid, AdditionalData = "€47" }; // Act var actualSerializedData = testSerializer.Serialize(token); var deserializedToken = testSerializer.Deserialize(actualSerializedData); // Assert AssertTokensEqual(token, deserializedToken); _dataProtector.Verify(); } [Fact] public void Serialize_FieldToken_WithUsername_TokenRoundTripSuccessful() { // Arrange var testSerializer = new AntiForgeryTokenSerializer(_dataProtector.Object); //"01" // Version //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken //+ "00" // IsSessionToken //+ "00" // IsClaimsBased //+ "08" // Username length header //+ "4AC3A972C3B46D65" // Username ("Jérôme") as UTF8 //+ "05" // AdditionalData length header //+ "E282AC3437"; // AdditionalData ("€47") as UTF8 var token = new AntiForgeryToken() { SecurityToken = _securityToken, IsSessionToken = false, Username = "Jérôme", AdditionalData = "€47" }; // Act var actualSerializedData = testSerializer.Serialize(token); var deserializedToken = testSerializer.Deserialize(actualSerializedData); // Assert AssertTokensEqual(token, deserializedToken); _dataProtector.Verify(); } [Fact] public void Serialize_SessionToken_TokenRoundTripSuccessful() { // Arrange var testSerializer = new AntiForgeryTokenSerializer(_dataProtector.Object); //"01" // Version //+ "705EEDCC7D42F1D6B3B98A593625BB4C" // SecurityToken //+ "01"; // IsSessionToken var token = new AntiForgeryToken() { SecurityToken = _securityToken, IsSessionToken = true }; // Act string actualSerializedData = testSerializer.Serialize(token); var deserializedToken = testSerializer.Deserialize(actualSerializedData); // Assert AssertTokensEqual(token, deserializedToken); _dataProtector.Verify(); } private static Mock GetDataProtector() { var mockCryptoSystem = new Mock(); mockCryptoSystem.Setup(o => o.Protect(It.IsAny())) .Returns(Protect) .Verifiable(); mockCryptoSystem.Setup(o => o.Unprotect(It.IsAny())) .Returns(UnProtect) .Verifiable(); return mockCryptoSystem; } private static byte[] Protect(byte[] data) { var input = new List(data); input.Add(_salt); return input.ToArray(); } private static byte[] UnProtect(byte[] data) { var salt = data[data.Length - 1]; if (salt != _salt) { throw new ArgumentException("Invalid salt value in data"); } return data.Take(data.Length - 1).ToArray(); } private static void AssertTokensEqual(AntiForgeryToken expected, AntiForgeryToken actual) { Assert.NotNull(expected); Assert.NotNull(actual); Assert.Equal(expected.AdditionalData, actual.AdditionalData); Assert.Equal(expected.ClaimUid, actual.ClaimUid); Assert.Equal(expected.IsSessionToken, actual.IsSessionToken); Assert.Equal(expected.SecurityToken, actual.SecurityToken); Assert.Equal(expected.Username, actual.Username); } } }