Port more security tests from Katana.
This commit is contained in:
parent
d5ce9fe736
commit
05804c78db
10
Security.sln
10
Security.sln
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.21813.0
|
VisualStudioVersion = 14.0.21916.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -15,7 +15,13 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CookieSample", "samples\Coo
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7BF11F3A-60B6-4796-B504-579C67FFBA34}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7BF11F3A-60B6-4796-B504-579C67FFBA34}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Security;
|
||||||
using Microsoft.AspNet.Security.Infrastructure;
|
using Microsoft.AspNet.Security.Infrastructure;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
|
|
||||||
|
|
@ -120,17 +121,28 @@ namespace Microsoft.AspNet.Security.Cookies
|
||||||
signin.Properties,
|
signin.Properties,
|
||||||
cookieOptions);
|
cookieOptions);
|
||||||
|
|
||||||
DateTimeOffset issuedUtc = Options.SystemClock.UtcNow;
|
DateTimeOffset issuedUtc;
|
||||||
DateTimeOffset expiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
|
if (signin.Properties.IssuedUtc.HasValue)
|
||||||
|
{
|
||||||
|
issuedUtc = signin.Properties.IssuedUtc.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
issuedUtc = Options.SystemClock.UtcNow;
|
||||||
|
signin.Properties.IssuedUtc = issuedUtc;
|
||||||
|
}
|
||||||
|
|
||||||
context.Properties.IssuedUtc = issuedUtc;
|
if (!signin.Properties.ExpiresUtc.HasValue)
|
||||||
context.Properties.ExpiresUtc = expiresUtc;
|
{
|
||||||
|
signin.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
Options.Notifications.ResponseSignIn(context);
|
Options.Notifications.ResponseSignIn(context);
|
||||||
|
|
||||||
if (context.Properties.IsPersistent)
|
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);
|
var model = new AuthenticationTicket(context.Identity, context.Properties);
|
||||||
|
|
@ -229,18 +241,27 @@ namespace Microsoft.AspNet.Security.Cookies
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string currentUri =
|
string loginUri = string.Empty;
|
||||||
Request.PathBase +
|
if (ChallengeContext != null)
|
||||||
Request.Path +
|
{
|
||||||
Request.QueryString;
|
loginUri = new AuthenticationProperties(ChallengeContext.Properties).RedirectUri;
|
||||||
|
}
|
||||||
|
|
||||||
string loginUri =
|
if (string.IsNullOrWhiteSpace(loginUri))
|
||||||
Request.Scheme +
|
{
|
||||||
"://" +
|
string currentUri =
|
||||||
Request.Host +
|
Request.PathBase +
|
||||||
Request.PathBase +
|
Request.Path +
|
||||||
Options.LoginPath +
|
Request.QueryString;
|
||||||
new QueryString(Options.ReturnUrlParameter, currentUri);
|
|
||||||
|
loginUri =
|
||||||
|
Request.Scheme +
|
||||||
|
"://" +
|
||||||
|
Request.Host +
|
||||||
|
Request.PathBase +
|
||||||
|
Options.LoginPath +
|
||||||
|
new QueryString(Options.ReturnUrlParameter, currentUri);
|
||||||
|
}
|
||||||
|
|
||||||
var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri);
|
var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri);
|
||||||
Options.Notifications.ApplyRedirect(redirectContext);
|
Options.Notifications.ApplyRedirect(redirectContext);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,11 +17,20 @@
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Content Include="katanatest.redmond.corp.microsoft.com.cer" />
|
||||||
<Content Include="project.json" />
|
<Content Include="project.json" />
|
||||||
|
<Content Include="selfSigned.cer" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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="DefaultAuthorizationServiceTests.cs" />
|
||||||
<Compile Include="FakePolicy.cs" />
|
<Compile Include="FakePolicy.cs" />
|
||||||
|
<Compile Include="SecurityHelperTests.cs" />
|
||||||
|
<Compile Include="TestClock.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -4,6 +4,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Security" : "",
|
"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",
|
"Moq": "4.2.1312.1622",
|
||||||
"Xunit.KRunner": "1.0.0-*"
|
"Xunit.KRunner": "1.0.0-*"
|
||||||
},
|
},
|
||||||
|
|
@ -13,7 +16,10 @@
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net45": {
|
"net45": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Runtime": ""
|
"Shouldly": "1.1.1.1",
|
||||||
|
"System.Runtime": "",
|
||||||
|
"System.Xml": "",
|
||||||
|
"System.Xml.Linq": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue