From 05804c78dbdf70da1f201ca7008a93f4720148c2 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Wed, 6 Aug 2014 15:28:09 -0700 Subject: [PATCH] Port more security tests from Katana. --- Security.sln | 10 +- .../CookieAuthenticationHandler.cs | 55 +- ...icateSubjectKeyIdentifierValidatorTests.cs | 123 +++++ ...icateSubjectPublicKeyInfoValidatorTests.cs | 173 +++++++ .../CertificateThumbprintValidatorTests.cs | 121 +++++ .../Cookies/CookieMiddlewareTests.cs | 479 ++++++++++++++++++ .../Encoder/Base64UrlTextEncoderTests.cs | 32 ++ ... => Microsoft.AspNet.Security.Tests.kproj} | 11 +- .../SecurityHelperTests.cs | 103 ++++ .../TestClock.cs | 23 + .../katanatest.redmond.corp.microsoft.com.cer | Bin 0 -> 1462 bytes .../project.json | 8 +- .../selfSigned.cer | Bin 0 -> 762 bytes 13 files changed, 1117 insertions(+), 21 deletions(-) create mode 100644 test/Microsoft.AspNet.Security.Test/CertificateSubjectKeyIdentifierValidatorTests.cs create mode 100644 test/Microsoft.AspNet.Security.Test/CertificateSubjectPublicKeyInfoValidatorTests.cs create mode 100644 test/Microsoft.AspNet.Security.Test/CertificateThumbprintValidatorTests.cs create mode 100644 test/Microsoft.AspNet.Security.Test/Cookies/CookieMiddlewareTests.cs create mode 100644 test/Microsoft.AspNet.Security.Test/DataHandler/Encoder/Base64UrlTextEncoderTests.cs rename test/Microsoft.AspNet.Security.Test/{Microsoft.AspNet.Security.kproj => Microsoft.AspNet.Security.Tests.kproj} (70%) create mode 100644 test/Microsoft.AspNet.Security.Test/SecurityHelperTests.cs create mode 100644 test/Microsoft.AspNet.Security.Test/TestClock.cs create mode 100644 test/Microsoft.AspNet.Security.Test/katanatest.redmond.corp.microsoft.com.cer create mode 100644 test/Microsoft.AspNet.Security.Test/selfSigned.cer diff --git a/Security.sln b/Security.sln index 39d830ee21..268ecf4a6c 100644 --- a/Security.sln +++ b/Security.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.21813.0 +VisualStudioVersion = 14.0.21916.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}" EndProject @@ -15,7 +15,13 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CookieSample", "samples\Coo EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7BF11F3A-60B6-4796-B504-579C67FFBA34}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security", "test\Microsoft.AspNet.Security.Test\Microsoft.AspNet.Security.kproj", "{8DA26CD1-1302-4CFD-9270-9FA1B7C6138B}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.Tests", "test\Microsoft.AspNet.Security.Test\Microsoft.AspNet.Security.Tests.kproj", "{8DA26CD1-1302-4CFD-9270-9FA1B7C6138B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C40A5A3B-ABA3-4819-9C44-D821E6DA1BA1}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationHandler.cs index 44d81db7f2..1e21937fab 100644 --- a/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationHandler.cs @@ -5,6 +5,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Security; using Microsoft.AspNet.Security.Infrastructure; using Microsoft.Framework.Logging; @@ -120,17 +121,28 @@ namespace Microsoft.AspNet.Security.Cookies signin.Properties, cookieOptions); - DateTimeOffset issuedUtc = Options.SystemClock.UtcNow; - DateTimeOffset expiresUtc = issuedUtc.Add(Options.ExpireTimeSpan); + DateTimeOffset issuedUtc; + if (signin.Properties.IssuedUtc.HasValue) + { + issuedUtc = signin.Properties.IssuedUtc.Value; + } + else + { + issuedUtc = Options.SystemClock.UtcNow; + signin.Properties.IssuedUtc = issuedUtc; + } - context.Properties.IssuedUtc = issuedUtc; - context.Properties.ExpiresUtc = expiresUtc; + if (!signin.Properties.ExpiresUtc.HasValue) + { + signin.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan); + } Options.Notifications.ResponseSignIn(context); if (context.Properties.IsPersistent) { - cookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime; + DateTimeOffset expiresUtc = context.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan); + context.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime; } var model = new AuthenticationTicket(context.Identity, context.Properties); @@ -229,18 +241,27 @@ namespace Microsoft.AspNet.Security.Cookies return; } - string currentUri = - Request.PathBase + - Request.Path + - Request.QueryString; - - string loginUri = - Request.Scheme + - "://" + - Request.Host + - Request.PathBase + - Options.LoginPath + - new QueryString(Options.ReturnUrlParameter, currentUri); + string loginUri = string.Empty; + if (ChallengeContext != null) + { + loginUri = new AuthenticationProperties(ChallengeContext.Properties).RedirectUri; + } + + if (string.IsNullOrWhiteSpace(loginUri)) + { + string currentUri = + Request.PathBase + + Request.Path + + Request.QueryString; + + loginUri = + Request.Scheme + + "://" + + Request.Host + + Request.PathBase + + Options.LoginPath + + new QueryString(Options.ReturnUrlParameter, currentUri); + } var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri); Options.Notifications.ApplyRedirect(redirectContext); diff --git a/test/Microsoft.AspNet.Security.Test/CertificateSubjectKeyIdentifierValidatorTests.cs b/test/Microsoft.AspNet.Security.Test/CertificateSubjectKeyIdentifierValidatorTests.cs new file mode 100644 index 0000000000..4771b98308 --- /dev/null +++ b/test/Microsoft.AspNet.Security.Test/CertificateSubjectKeyIdentifierValidatorTests.cs @@ -0,0 +1,123 @@ +// 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.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Shouldly; +using Xunit; + +namespace Microsoft.AspNet.Security +{ + public class CertificateSubjectKeyIdentifierValidatorTests + { + private static readonly X509Certificate2 SelfSigned = new X509Certificate2("selfSigned.cer"); + private static readonly X509Certificate2 Chained = new X509Certificate2("katanatest.redmond.corp.microsoft.com.cer"); + + // The Katana test cert has a valid full chain + // katanatest.redmond.corp.microsoft.com -> MSIT Machine Auth CA2 -> Microsoft Internet Authority -> Baltimore CyberTrustRoot + + private const string KatanaTestKeyIdentifier = "d964b2941aaf3e62761041b1f3db098edfa3270a"; + private const string MicrosoftInternetAuthorityKeyIdentifier = "2a4d97955d347e9db6e633be9c27c1707e67dbc1"; + + [Fact] + public void ConstructorShouldNotThrowWithValidValues() + { + var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty }); + + instance.ShouldNotBe(null); + } + + [Fact] + public void ConstructorShouldThrownWhenTheValidHashEnumerableIsNull() + { + Should.Throw(() => + new CertificateSubjectKeyIdentifierValidator(null)); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateChainErrors() + { + var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty }); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateChainErrors); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNameMismatch() + { + var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty }); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNameMismatch); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNotAvailable() + { + var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty }); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNotAvailable); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenPassedASelfSignedCertificate() + { + var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty }); + var certificateChain = new X509Chain(); + certificateChain.Build(SelfSigned); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, SelfSigned, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenPassedATrustedCertificateWhichDoesNotHaveAWhitelistedSubjectKeyIdentifier() + { + var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty }); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasItsSubjectKeyIdentifierWhiteListed() + { + var instance = new CertificateSubjectKeyIdentifierValidator( + new[] + { + KatanaTestKeyIdentifier + }); + + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(true); + } + + [Fact] + public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasAChainElementSubjectKeyIdentifierWhiteListed() + { + var instance = new CertificateSubjectKeyIdentifierValidator( + new[] + { + MicrosoftInternetAuthorityKeyIdentifier + }); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(true); + } + } +} diff --git a/test/Microsoft.AspNet.Security.Test/CertificateSubjectPublicKeyInfoValidatorTests.cs b/test/Microsoft.AspNet.Security.Test/CertificateSubjectPublicKeyInfoValidatorTests.cs new file mode 100644 index 0000000000..28270cd480 --- /dev/null +++ b/test/Microsoft.AspNet.Security.Test/CertificateSubjectPublicKeyInfoValidatorTests.cs @@ -0,0 +1,173 @@ +// 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.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Shouldly; +using Xunit; + +namespace Microsoft.AspNet.Security +{ + public class CertificateSubjectPublicKeyInfoValidatorTests + { + private static readonly X509Certificate2 SelfSigned = new X509Certificate2("selfSigned.cer"); + private static readonly X509Certificate2 Chained = new X509Certificate2("katanatest.redmond.corp.microsoft.com.cer"); + + // The Katana test cert has a valid full chain + // katanatest.redmond.corp.microsoft.com -> MSIT Machine Auth CA2 -> Microsoft Internet Authority -> Baltimore CyberTrustRoot + + // The following fingerprints were generated using the go program in appendix A of the Public Key Pinning Extension for HTTP + // draft-ietf-websec-key-pinning-05 + + private const string KatanaTestSha1Hash = "xvNsCWwxvL3qsCYChZLiwNm1D6o="; + private const string KatanaTestSha256Hash = "AhR1Y/xhxK2uD7YJ0xKUPq8tYrWm4+F7DgO2wUOqB+4="; + + private const string MicrosoftInternetAuthoritySha1Hash = "Z3HnseSVDEPu5hZoj05/bBSnT/s="; + private const string MicrosoftInternetAuthoritySha256Hash = "UQTPeq/Tlg/vLt2ijtl7qlMFBFkbGG9aAWJbQMOMWFg="; + + [Fact] + public void ConstructorShouldNotThrowWithValidValues() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1); + + instance.ShouldNotBe(null); + } + + [Fact] + public void ConstructorShouldThrownWhenTheValidHashEnumerableIsNull() + { + Should.Throw(() => + new CertificateSubjectPublicKeyInfoValidator(null, SubjectPublicKeyInfoAlgorithm.Sha1)); + } + + [Fact] + public void ConstructorShouldThrowWhenTheHashEnumerableContainsNoHashes() + { + Should.Throw(() => + new CertificateSubjectPublicKeyInfoValidator(new string[0], SubjectPublicKeyInfoAlgorithm.Sha1)); + } + + [Fact] + public void ConstructorShouldThrowIfAnInvalidAlgorithmIsPassed() + { + Should.Throw(() => + new CertificateSubjectPublicKeyInfoValidator(new string[0], (SubjectPublicKeyInfoAlgorithm)2)); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateChainErrors() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateChainErrors); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNameMismatch() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNameMismatch); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNotAvailable() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNotAvailable); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenPassedASelfSignedCertificate() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1); + var certificateChain = new X509Chain(); + certificateChain.Build(SelfSigned); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, SelfSigned, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenPassedATrustedCertificateWhichDoesNotHaveAWhitelistedSha1Spki() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasItsSha1SpkiWhiteListed() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new[] { KatanaTestSha1Hash }, SubjectPublicKeyInfoAlgorithm.Sha1); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(true); + } + + [Fact] + public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasAChainElementSha1SpkiWhiteListed() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new[] { MicrosoftInternetAuthoritySha1Hash }, SubjectPublicKeyInfoAlgorithm.Sha1); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(true); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenPassedATrustedCertificateWhichDoesNotHaveAWhitelistedSha256Spki() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha256); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasItsSha256SpkiWhiteListed() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new[] { KatanaTestSha256Hash }, SubjectPublicKeyInfoAlgorithm.Sha256); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(true); + } + + [Fact] + public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasAChainElementSha256SpkiWhiteListed() + { + var instance = new CertificateSubjectPublicKeyInfoValidator(new[] { MicrosoftInternetAuthoritySha256Hash }, SubjectPublicKeyInfoAlgorithm.Sha256); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(true); + } + } +} diff --git a/test/Microsoft.AspNet.Security.Test/CertificateThumbprintValidatorTests.cs b/test/Microsoft.AspNet.Security.Test/CertificateThumbprintValidatorTests.cs new file mode 100644 index 0000000000..82e4a3a3dc --- /dev/null +++ b/test/Microsoft.AspNet.Security.Test/CertificateThumbprintValidatorTests.cs @@ -0,0 +1,121 @@ +// 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.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Shouldly; +using Xunit; + +namespace Microsoft.AspNet.Security +{ + public class CertificateThumbprintValidatorTests + { + private static readonly X509Certificate2 SelfSigned = new X509Certificate2("selfSigned.cer"); + private static readonly X509Certificate2 Chained = new X509Certificate2("katanatest.redmond.corp.microsoft.com.cer"); + + // The Katana test cert has a valid full chain + // katanatest.redmond.corp.microsoft.com -> MSIT Machine Auth CA2 -> Microsoft Internet Authority -> Baltimore CyberTrustRoot + + private const string KatanaTestThumbprint = "a9894c464b260cac3f5b91cece33b3c55e82e61c"; + private const string MicrosoftInternetAuthorityThumbprint = "992ad44d7dce298de17e6f2f56a7b9caa41db93f"; + + [Fact] + public void ConstructorShouldNotThrowWithValidValues() + { + var instance = new CertificateThumbprintValidator(new string[1]); + + instance.ShouldNotBe(null); + } + + [Fact] + public void ConstructorShouldThrownWhenTheValidHashEnumerableIsNull() + { + Should.Throw(() => + new CertificateThumbprintValidator(null)); + } + + [Fact] + public void ConstructorShouldThrowWhenTheHashEnumerableContainsNoHashes() + { + Should.Throw(() => + new CertificateThumbprintValidator(new string[0])); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateChainErrors() + { + var instance = new CertificateThumbprintValidator(new string[1]); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateChainErrors); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNameMismatch() + { + var instance = new CertificateThumbprintValidator(new string[1]); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNameMismatch); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNotAvailable() + { + var instance = new CertificateThumbprintValidator(new string[1]); + bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNotAvailable); + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenPassedASelfSignedCertificate() + { + var instance = new CertificateThumbprintValidator(new string[1]); + var certificateChain = new X509Chain(); + certificateChain.Build(SelfSigned); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, SelfSigned, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnFalseWhenPassedATrustedCertificateWhichDoesNotHaveAWhitelistedThumbprint() + { + var instance = new CertificateThumbprintValidator(new string[1]); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(false); + } + + [Fact] + public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasItsThumbprintWhiteListed() + { + var instance = new CertificateThumbprintValidator(new[] { KatanaTestThumbprint }); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(true); + } + + [Fact] + public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasAChainElementThumbprintWhiteListed() + { + var instance = new CertificateThumbprintValidator(new[] { MicrosoftInternetAuthorityThumbprint }); + var certificateChain = new X509Chain(); + certificateChain.Build(Chained); + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None); + + result.ShouldBe(true); + } + } +} diff --git a/test/Microsoft.AspNet.Security.Test/Cookies/CookieMiddlewareTests.cs b/test/Microsoft.AspNet.Security.Test/Cookies/CookieMiddlewareTests.cs new file mode 100644 index 0000000000..6578ba6004 --- /dev/null +++ b/test/Microsoft.AspNet.Security.Test/Cookies/CookieMiddlewareTests.cs @@ -0,0 +1,479 @@ +// 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.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Claims; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Security; +using Microsoft.AspNet.TestHost; +using Shouldly; +using Xunit; + +namespace Microsoft.AspNet.Security.Cookies +{ + public class CookieMiddlewareTests + { + [Fact] + public async Task NormalRequestPassesThrough() + { + TestServer server = CreateServer(new CookieAuthenticationOptions + { + }); + HttpResponseMessage response = await server.CreateClient().GetAsync("http://example.com/normal"); + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + [Fact] + public async Task ProtectedRequestShouldRedirectToLogin() + { + TestServer server = CreateServer(new CookieAuthenticationOptions + { + LoginPath = new PathString("/login") + }); + + Transaction transaction = await SendAsync(server, "http://example.com/protected"); + + transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); + + Uri location = transaction.Response.Headers.Location; + location.LocalPath.ShouldBe("/login"); + location.Query.ShouldBe("?ReturnUrl=%2Fprotected"); + } + + [Fact] + public async Task ProtectedCustomRequestShouldRedirectToCustomLogin() + { + TestServer server = CreateServer(new CookieAuthenticationOptions + { + LoginPath = new PathString("/login") + }); + + Transaction transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect"); + + transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); + + Uri location = transaction.Response.Headers.Location; + location.ToString().ShouldBe("/CustomRedirect"); + } + + private Task SignInAsAlice(HttpContext context) + { + context.Response.SignIn( + new AuthenticationProperties(), + new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))); + return Task.FromResult(null); + } + + [Fact] + public async Task SignInCausesDefaultCookieToBeCreated() + { + TestServer server = CreateServer(new CookieAuthenticationOptions + { + LoginPath = new PathString("/login"), + CookieName = "TestCookie", + }, SignInAsAlice); + + Transaction transaction = await SendAsync(server, "http://example.com/testpath"); + + string setCookie = transaction.SetCookie; + setCookie.ShouldStartWith("TestCookie="); + setCookie.ShouldContain("; path=/"); + setCookie.ShouldContain("; HttpOnly"); + setCookie.ShouldNotContain("; expires="); + setCookie.ShouldNotContain("; domain="); + setCookie.ShouldNotContain("; secure"); + } + + [Theory] + [InlineData(CookieSecureOption.Always, "http://example.com/testpath", true)] + [InlineData(CookieSecureOption.Always, "https://example.com/testpath", true)] + [InlineData(CookieSecureOption.Never, "http://example.com/testpath", false)] + [InlineData(CookieSecureOption.Never, "https://example.com/testpath", false)] + [InlineData(CookieSecureOption.SameAsRequest, "http://example.com/testpath", false)] + [InlineData(CookieSecureOption.SameAsRequest, "https://example.com/testpath", true)] + public async Task SecureSignInCausesSecureOnlyCookieByDefault( + CookieSecureOption cookieSecureOption, + string requestUri, + bool shouldBeSecureOnly) + { + TestServer server = CreateServer(new CookieAuthenticationOptions + { + LoginPath = new PathString("/login"), + CookieName = "TestCookie", + CookieSecure = cookieSecureOption + }, SignInAsAlice); + + Transaction transaction = await SendAsync(server, requestUri); + string setCookie = transaction.SetCookie; + + if (shouldBeSecureOnly) + { + setCookie.ShouldContain("; secure"); + } + else + { + setCookie.ShouldNotContain("; secure"); + } + } + + [Fact] + public async Task CookieOptionsAlterSetCookieHeader() + { + TestServer server1 = CreateServer(new CookieAuthenticationOptions + { + CookieName = "TestCookie", + CookiePath = "/foo", + CookieDomain = "another.com", + CookieSecure = CookieSecureOption.Always, + CookieHttpOnly = true, + }, SignInAsAlice); + + Transaction transaction1 = await SendAsync(server1, "http://example.com/testpath"); + + TestServer server2 = CreateServer(new CookieAuthenticationOptions + { + CookieName = "SecondCookie", + CookieSecure = CookieSecureOption.Never, + CookieHttpOnly = false, + }, SignInAsAlice); + + Transaction transaction2 = await SendAsync(server2, "http://example.com/testpath"); + + string setCookie1 = transaction1.SetCookie; + string setCookie2 = transaction2.SetCookie; + + setCookie1.ShouldContain("TestCookie="); + setCookie1.ShouldContain(" path=/foo"); + setCookie1.ShouldContain(" domain=another.com"); + setCookie1.ShouldContain(" secure"); + setCookie1.ShouldContain(" HttpOnly"); + + setCookie2.ShouldContain("SecondCookie="); + setCookie2.ShouldNotContain(" domain="); + setCookie2.ShouldNotContain(" secure"); + setCookie2.ShouldNotContain(" HttpOnly"); + } + + [Fact] + public async Task CookieContainsIdentity() + { + var clock = new TestClock(); + TestServer server = CreateServer(new CookieAuthenticationOptions + { + SystemClock = clock + }, SignInAsAlice); + + Transaction transaction1 = await SendAsync(server, "http://example.com/testpath"); + + Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice"); + } + + [Fact] + public async Task CookieStopsWorkingAfterExpiration() + { + var clock = new TestClock(); + TestServer server = CreateServer(new CookieAuthenticationOptions + { + SystemClock = clock, + ExpireTimeSpan = TimeSpan.FromMinutes(10), + SlidingExpiration = false, + }, SignInAsAlice); + + Transaction transaction1 = await SendAsync(server, "http://example.com/testpath"); + + Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(7)); + + Transaction transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(7)); + + Transaction transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + transaction2.SetCookie.ShouldBe(null); + FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice"); + transaction3.SetCookie.ShouldBe(null); + FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("Alice"); + transaction4.SetCookie.ShouldBe(null); + FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe(null); + } + + [Fact] + public async Task CookieExpirationCanBeOverridenInSignin() + { + var clock = new TestClock(); + TestServer server = CreateServer(new CookieAuthenticationOptions + { + SystemClock = clock, + ExpireTimeSpan = TimeSpan.FromMinutes(10), + SlidingExpiration = false, + }, + context => + { + context.Response.SignIn( + new AuthenticationProperties() { ExpiresUtc = clock.UtcNow.Add(TimeSpan.FromMinutes(5)) }, + new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))); + return Task.FromResult(null); + }); + + Transaction transaction1 = await SendAsync(server, "http://example.com/testpath"); + + Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(3)); + + Transaction transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(3)); + + Transaction transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + transaction2.SetCookie.ShouldBe(null); + FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice"); + transaction3.SetCookie.ShouldBe(null); + FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("Alice"); + transaction4.SetCookie.ShouldBe(null); + FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe(null); + } + + [Fact] + public async Task CookieExpirationCanBeOverridenInEvent() + { + var clock = new TestClock(); + TestServer server = CreateServer(new CookieAuthenticationOptions + { + SystemClock = clock, + ExpireTimeSpan = TimeSpan.FromMinutes(10), + SlidingExpiration = false, + Notifications = new CookieAuthenticationNotifications() + { + OnResponseSignIn = context => + { + context.Properties.ExpiresUtc = clock.UtcNow.Add(TimeSpan.FromMinutes(5)); + } + } + }, SignInAsAlice); + + Transaction transaction1 = await SendAsync(server, "http://example.com/testpath"); + + Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(3)); + + Transaction transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(3)); + + Transaction transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + transaction2.SetCookie.ShouldBe(null); + FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice"); + transaction3.SetCookie.ShouldBe(null); + FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("Alice"); + transaction4.SetCookie.ShouldBe(null); + FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe(null); + } + + [Fact] + public async Task CookieIsRenewedWithSlidingExpiration() + { + var clock = new TestClock(); + TestServer server = CreateServer(new CookieAuthenticationOptions + { + SystemClock = clock, + ExpireTimeSpan = TimeSpan.FromMinutes(10), + SlidingExpiration = true, + }, SignInAsAlice); + + Transaction transaction1 = await SendAsync(server, "http://example.com/testpath"); + + Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(4)); + + Transaction transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(4)); + + // transaction4 should arrive with a new SetCookie value + Transaction transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue); + + clock.Add(TimeSpan.FromMinutes(4)); + + Transaction transaction5 = await SendAsync(server, "http://example.com/me/Cookies", transaction4.CookieNameValue); + + transaction2.SetCookie.ShouldBe(null); + FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice"); + transaction3.SetCookie.ShouldBe(null); + FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("Alice"); + transaction4.SetCookie.ShouldNotBe(null); + FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe("Alice"); + transaction5.SetCookie.ShouldBe(null); + FindClaimValue(transaction5, ClaimTypes.Name).ShouldBe("Alice"); + } + + [Fact] + public async Task AjaxRedirectsAsExtraHeaderOnTwoHundred() + { + TestServer server = CreateServer(new CookieAuthenticationOptions + { + LoginPath = new PathString("/login") + }); + + Transaction transaction = await SendAsync(server, "http://example.com/protected", ajaxRequest: true); + + transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + var responded = transaction.Response.Headers.GetValues("X-Responded-JSON"); + + responded.Count().ShouldBe(1); + responded.Single().ShouldContain("\"location\""); + } + + private static string FindClaimValue(Transaction transaction, string claimType) + { + XElement claim = transaction.ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType); + if (claim == null) + { + return null; + } + return claim.Attribute("value").Value; + } + + private static async Task GetAuthData(TestServer server, string url, string cookie) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("Cookie", cookie); + + HttpResponseMessage response2 = await server.CreateClient().SendAsync(request); + string text = await response2.Content.ReadAsStringAsync(); + XElement me = XElement.Parse(text); + return me; + } + + private static TestServer CreateServer(CookieAuthenticationOptions options, Func testpath = null) + { + return TestServer.Create(app => + { + app.UseCookieAuthentication(options); + app.Use(async (context, next) => + { + var req = context.Request; + var res = context.Response; + PathString remainder; + if (req.Path == new PathString("/normal")) + { + res.StatusCode = 200; + } + else if (req.Path == new PathString("/protected")) + { + res.StatusCode = 401; + } + else if (req.Path == new PathString("/protected/CustomRedirect")) + { + context.Response.Challenge(new AuthenticationProperties() { RedirectUri = "/CustomRedirect" }); + } + else if (req.Path == new PathString("/me")) + { + Describe(res, new AuthenticationResult(context.User.Identity, new AuthenticationProperties(), new AuthenticationDescription())); + } + else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder)) + { + var result = await context.AuthenticateAsync(remainder.Value.Substring(1)); + Describe(res, result); + } + else if (req.Path == new PathString("/testpath") && testpath != null) + { + await testpath(context); + } + else + { + await next(); + } + }); + }); + } + + private static void Describe(HttpResponse res, AuthenticationResult result) + { + res.StatusCode = 200; + res.ContentType = "text/xml"; + var xml = new XElement("xml"); + if (result != null && result.Identity != null) + { + xml.Add(result.Identity.Claims.Select(claim => new XElement("claim", new XAttribute("type", claim.Type), new XAttribute("value", claim.Value)))); + } + if (result != null && result.Properties != null) + { + xml.Add(result.Properties.Dictionary.Select(extra => new XElement("extra", new XAttribute("type", extra.Key), new XAttribute("value", extra.Value)))); + } + using (var memory = new MemoryStream()) + { + using (var writer = new XmlTextWriter(memory, Encoding.UTF8)) + { + xml.WriteTo(writer); + } + res.Body.Write(memory.ToArray(), 0, memory.ToArray().Length); + } + } + + private static async Task SendAsync(TestServer server, string uri, string cookieHeader = null, bool ajaxRequest = false) + { + var request = new HttpRequestMessage(HttpMethod.Get, uri); + if (!string.IsNullOrEmpty(cookieHeader)) + { + request.Headers.Add("Cookie", cookieHeader); + } + if (ajaxRequest) + { + request.Headers.Add("X-Requested-With", "XMLHttpRequest"); + } + var transaction = new Transaction + { + Request = request, + Response = await server.CreateClient().SendAsync(request), + }; + if (transaction.Response.Headers.Contains("Set-Cookie")) + { + transaction.SetCookie = transaction.Response.Headers.GetValues("Set-Cookie").SingleOrDefault(); + } + if (!string.IsNullOrEmpty(transaction.SetCookie)) + { + transaction.CookieNameValue = transaction.SetCookie.Split(new[] { ';' }, 2).First(); + } + transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync(); + + if (transaction.Response.Content != null && + transaction.Response.Content.Headers.ContentType != null && + transaction.Response.Content.Headers.ContentType.MediaType == "text/xml") + { + transaction.ResponseElement = XElement.Parse(transaction.ResponseText); + } + return transaction; + } + + private class Transaction + { + public HttpRequestMessage Request { get; set; } + public HttpResponseMessage Response { get; set; } + + public string SetCookie { get; set; } + public string CookieNameValue { get; set; } + + public string ResponseText { get; set; } + public XElement ResponseElement { get; set; } + } + } +} diff --git a/test/Microsoft.AspNet.Security.Test/DataHandler/Encoder/Base64UrlTextEncoderTests.cs b/test/Microsoft.AspNet.Security.Test/DataHandler/Encoder/Base64UrlTextEncoderTests.cs new file mode 100644 index 0000000000..be97f2f12c --- /dev/null +++ b/test/Microsoft.AspNet.Security.Test/DataHandler/Encoder/Base64UrlTextEncoderTests.cs @@ -0,0 +1,32 @@ +// 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 Shouldly; +using Xunit; + +namespace Microsoft.AspNet.Security.DataHandler.Encoder +{ + public class Base64UrlTextEncoderTests + { + [Fact] + public void DataOfVariousLengthRoundTripCorrectly() + { + var encoder = new Base64UrlTextEncoder(); + for (int length = 0; length != 256; ++length) + { + var data = new byte[length]; + for (int index = 0; index != length; ++index) + { + data[index] = (byte)(5 + length + (index * 23)); + } + string text = encoder.Encode(data); + byte[] result = encoder.Decode(text); + + for (int index = 0; index != length; ++index) + { + result[index].ShouldBe(data[index]); + } + } + } + } +} diff --git a/test/Microsoft.AspNet.Security.Test/Microsoft.AspNet.Security.kproj b/test/Microsoft.AspNet.Security.Test/Microsoft.AspNet.Security.Tests.kproj similarity index 70% rename from test/Microsoft.AspNet.Security.Test/Microsoft.AspNet.Security.kproj rename to test/Microsoft.AspNet.Security.Test/Microsoft.AspNet.Security.Tests.kproj index 62a9b7453a..903c63d854 100644 --- a/test/Microsoft.AspNet.Security.Test/Microsoft.AspNet.Security.kproj +++ b/test/Microsoft.AspNet.Security.Test/Microsoft.AspNet.Security.Tests.kproj @@ -17,11 +17,20 @@ 2.0 + + + + + + + + + - + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Security.Test/SecurityHelperTests.cs b/test/Microsoft.AspNet.Security.Test/SecurityHelperTests.cs new file mode 100644 index 0000000000..76f8f6e0a7 --- /dev/null +++ b/test/Microsoft.AspNet.Security.Test/SecurityHelperTests.cs @@ -0,0 +1,103 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.PipelineCore; +using Microsoft.AspNet.Security.Infrastructure; +using Shouldly; +using Xunit; + +namespace Microsoft.AspNet.Security +{ + public class SecurityHelperTests + { + [Fact] + public void AddingToAnonymousIdentityDoesNotKeepAnonymousIdentity() + { + HttpContext context = new DefaultHttpContext(); + context.User.ShouldNotBe(null); + context.User.Identity.IsAuthenticated.ShouldBe(false); + + SecurityHelper.AddUserIdentity(context, new GenericIdentity("Test1", "Alpha")); + + context.User.ShouldNotBe(null); + context.User.Identity.AuthenticationType.ShouldBe("Alpha"); + context.User.Identity.Name.ShouldBe("Test1"); + + context.User.ShouldBeTypeOf(); + context.User.Identity.ShouldBeTypeOf(); + + ((ClaimsPrincipal)context.User).Identities.Count().ShouldBe(1); + } + + [Fact] + public void AddingExistingIdentityChangesDefaultButPreservesPrior() + { + HttpContext context = new DefaultHttpContext(); + context.User = new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), null); + + context.User.Identity.AuthenticationType.ShouldBe("Alpha"); + context.User.Identity.Name.ShouldBe("Test1"); + + SecurityHelper.AddUserIdentity(context, new GenericIdentity("Test2", "Beta")); + + context.User.Identity.AuthenticationType.ShouldBe("Beta"); + context.User.Identity.Name.ShouldBe("Test2"); + + SecurityHelper.AddUserIdentity(context, new GenericIdentity("Test3", "Gamma")); + + context.User.Identity.AuthenticationType.ShouldBe("Gamma"); + context.User.Identity.Name.ShouldBe("Test3"); + + var principal = context.User; + principal.Identities.Count().ShouldBe(3); + principal.Identities.Skip(0).First().Name.ShouldBe("Test3"); + principal.Identities.Skip(1).First().Name.ShouldBe("Test2"); + principal.Identities.Skip(2).First().Name.ShouldBe("Test1"); + } + + [Fact] + public void NoChallengesMeansLookupsAreDeterminedOnlyByActiveOrPassiveMode() + { + HttpContext context = new DefaultHttpContext(); + + bool activeNoChallenge = SecurityHelper.LookupChallenge(new string[0], "Alpha", AuthenticationMode.Active); + bool passiveNoChallenge = SecurityHelper.LookupChallenge(new string[0], "Alpha", AuthenticationMode.Passive); + + context.Response.StatusCode = 401; + + bool activeEmptyChallenge = SecurityHelper.LookupChallenge(new string[0], "Alpha", AuthenticationMode.Active); + bool passiveEmptyChallenge = SecurityHelper.LookupChallenge(new string[0], "Alpha", AuthenticationMode.Passive); + + Assert.True(activeNoChallenge); + Assert.False(passiveNoChallenge); + Assert.True(activeEmptyChallenge); + Assert.False(passiveEmptyChallenge); + } + + [Fact] + public void WithChallengesMeansLookupsAreDeterminedOnlyByMatchingAuthenticationType() + { + HttpContext context = new DefaultHttpContext(); + + IEnumerable challengeTypes = new[] { "Beta", "Gamma" }; + + bool activeNoMatch = SecurityHelper.LookupChallenge(challengeTypes, "Alpha", AuthenticationMode.Active); + bool passiveNoMatch = SecurityHelper.LookupChallenge(challengeTypes, "Alpha", AuthenticationMode.Passive); + + challengeTypes = new[] { "Beta", "Alpha" }; + + bool activeWithMatch = SecurityHelper.LookupChallenge(challengeTypes, "Alpha", AuthenticationMode.Active); + bool passiveWithMatch = SecurityHelper.LookupChallenge(challengeTypes, "Alpha", AuthenticationMode.Passive); + + Assert.False(activeNoMatch); + Assert.False(passiveNoMatch); + Assert.True(activeWithMatch); + Assert.True(passiveWithMatch); + } + } +} diff --git a/test/Microsoft.AspNet.Security.Test/TestClock.cs b/test/Microsoft.AspNet.Security.Test/TestClock.cs new file mode 100644 index 0000000000..cd8f998397 --- /dev/null +++ b/test/Microsoft.AspNet.Security.Test/TestClock.cs @@ -0,0 +1,23 @@ +// 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 Microsoft.AspNet.Security.Infrastructure; + +namespace Microsoft.AspNet.Security +{ + public class TestClock : ISystemClock + { + public TestClock() + { + UtcNow = new DateTimeOffset(2013, 6, 11, 12, 34, 56, 789, TimeSpan.Zero); + } + + public DateTimeOffset UtcNow { get; set; } + + public void Add(TimeSpan timeSpan) + { + UtcNow = UtcNow + timeSpan; + } + } +} diff --git a/test/Microsoft.AspNet.Security.Test/katanatest.redmond.corp.microsoft.com.cer b/test/Microsoft.AspNet.Security.Test/katanatest.redmond.corp.microsoft.com.cer new file mode 100644 index 0000000000000000000000000000000000000000..bfd5220e2cf5a9f6d78145f62d2031a44ef50e59 GIT binary patch literal 1462 zcmXqLV%=oW#4>9EGZP~d6IaOeUIzw728M=S170>xtu~Lg@4SqRtgH+MjSYsv27+u{ zoRic#Cx6mOVU!eOPR`FYlr#`W5#-FxOfJeV&QB{b6fqD&kzz^CFDft;HxNY;WiLui z$<5D8F_bruWn&IyVHOti4fYIC@J&q4$jnPsa4ap!P;hosFfx!6=QT7oFf%eUv@|p~ zHj4ssO_8_;28MbDIuIjNvlB}a^Abx^i%ayN=IeoM*Mqwb$jvoqVpKx*CnGBZa}y&! z15licsfm%1;iL7N%^!0Xs~;%IKiIkMIp=T7M)sAbvfm5&d=}rTo8T~e-4vB`Z<@Tn z6m~?%cKum?@SLe?cETkEm1i*G#e7wJ^UKTx=NA{5m>C%u7dJ6g8#FPMgQG{5k420{``jI+p=AFz99ZSy-5vST-2QgE-185(Z)oBCl@?#{J-&cjH>poWDtPAGdgE zJXAMmdyi5M`oc|r{?FE7XKZDPD!V&H;gEV_FWGZTplxDo?r`#y!`~&1{>_KpwsQ2XywsY^#F)aw$OTC* zsw`p#A~5$bGr?Jm2I>g&xmctOBn-IVl1xZ!M&t|%EQo*^l#wC&=)e2#45dUlzKJv5 z`Rj3z&983bHLa%~FD?D`EY4xsTQ{$lvFl9Nx}NawQM%N_d4GZD^$zDH%lCXdVcdQv zDa^cgx{JY*=A+_En>a2Ce`)KfLw&_ciFG);W)r z(-!=F7v-`+iPTd%8 Dk#GvE literal 0 HcmV?d00001 diff --git a/test/Microsoft.AspNet.Security.Test/project.json b/test/Microsoft.AspNet.Security.Test/project.json index 68837d5bea..b46b261dec 100644 --- a/test/Microsoft.AspNet.Security.Test/project.json +++ b/test/Microsoft.AspNet.Security.Test/project.json @@ -4,6 +4,9 @@ }, "dependencies": { "Microsoft.AspNet.Security" : "", + "Microsoft.AspNet.Security.Cookies" : "", + "Microsoft.AspNet.TestHost": "1.0.0-*", + "System.Net.Http": "4.0.0.0", "Moq": "4.2.1312.1622", "Xunit.KRunner": "1.0.0-*" }, @@ -13,7 +16,10 @@ "frameworks": { "net45": { "dependencies": { - "System.Runtime": "" + "Shouldly": "1.1.1.1", + "System.Runtime": "", + "System.Xml": "", + "System.Xml.Linq": "" } } } diff --git a/test/Microsoft.AspNet.Security.Test/selfSigned.cer b/test/Microsoft.AspNet.Security.Test/selfSigned.cer new file mode 100644 index 0000000000000000000000000000000000000000..6acc7af5a606916b16c00aef0177b1e4858d7290 GIT binary patch literal 762 zcmXqLV)|y##Q10dGZP~dlR(?zA3ry9D6e$iICaDG9g9C1aI&##^D#5YvN9Nm8VVZ- zvN4CUFbi{eCzd4UC5EIHml(*2^BNi(7y_ZW0T4uq^BN&@acOH}R5IXYh&j54_7gG}>Bg5p{Ygf8TXGs{Hj>&cCUVOqs?tZJ;an`>l62y1kxZk6~ zuFIUiZ%3bNU>xhe=(iubQr+ix=sr_x=xJE8NI2KLQqo;fd(Gz=2b$I#RQrBm>AH-i zkKf$$tG(O&CFSU}8}lEB&lieWTW|EsKf7PUZrb(3?pt$Kb!0z}5Q&rz6-jM78g4V| zOK9EiW0L}SKCE8V|6ZL*&v??tg-6}5K9j#b@qni1DwnHlCKc7!6%DNGq>`U}x_Bt* z&8)Xu?r^5BnZzWM9eH~B>Cf(NE)%mvtAm{1mu~zL*wWUgc+@eEXB)@~?%T%FSzNdgJwziJ6gsaj}N};q$PvEvyx^x#(Bl{N+D2**46g^?wC$QZC9<=OZR2BCf#b{zy(PLnRJ+Rx zZ+pk)db%T3^V#*QS2}P! z;rBeTSMJYZ-nthS6M6nGRag%UASt!}7{?02=i_8vp