Port more security tests from Katana.

This commit is contained in:
Chris Ross 2014-08-06 15:28:09 -07:00
parent d5ce9fe736
commit 05804c78db
13 changed files with 1117 additions and 21 deletions

View File

@ -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

View File

@ -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);

View File

@ -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<ArgumentNullException>(() =>
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);
}
}
}

View File

@ -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<ArgumentNullException>(() =>
new CertificateSubjectPublicKeyInfoValidator(null, SubjectPublicKeyInfoAlgorithm.Sha1));
}
[Fact]
public void ConstructorShouldThrowWhenTheHashEnumerableContainsNoHashes()
{
Should.Throw<ArgumentOutOfRangeException>(() =>
new CertificateSubjectPublicKeyInfoValidator(new string[0], SubjectPublicKeyInfoAlgorithm.Sha1));
}
[Fact]
public void ConstructorShouldThrowIfAnInvalidAlgorithmIsPassed()
{
Should.Throw<ArgumentOutOfRangeException>(() =>
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);
}
}
}

View File

@ -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<ArgumentNullException>(() =>
new CertificateThumbprintValidator(null));
}
[Fact]
public void ConstructorShouldThrowWhenTheHashEnumerableContainsNoHashes()
{
Should.Throw<ArgumentOutOfRangeException>(() =>
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);
}
}
}

View File

@ -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<object>(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<object>(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<XElement> 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<HttpContext, Task> 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<Transaction> 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; }
}
}
}

View File

@ -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]);
}
}
}
}
}

View File

@ -17,11 +17,20 @@
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="katanatest.redmond.corp.microsoft.com.cer" />
<Content Include="project.json" />
<Content Include="selfSigned.cer" />
</ItemGroup>
<ItemGroup>
<Compile Include="CertificateSubjectKeyIdentifierValidatorTests.cs" />
<Compile Include="CertificateSubjectPublicKeyInfoValidatorTests.cs" />
<Compile Include="CertificateThumbprintValidatorTests.cs" />
<Compile Include="Cookies\CookieMiddlewareTests.cs" />
<Compile Include="DataHandler\Encoder\Base64UrlTextEncoderTests.cs" />
<Compile Include="DefaultAuthorizationServiceTests.cs" />
<Compile Include="FakePolicy.cs" />
<Compile Include="SecurityHelperTests.cs" />
<Compile Include="TestClock.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

View File

@ -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<ClaimsPrincipal>();
context.User.Identity.ShouldBeTypeOf<ClaimsIdentity>();
((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<string> 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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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": ""
}
}
}

Binary file not shown.