Implement client certificate authentication
This commit is contained in:
parent
7e8a405917
commit
bd30f28dfd
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Server.Kestrel.Https
|
||||||
|
{
|
||||||
|
public enum ClientCertificateMode
|
||||||
|
{
|
||||||
|
NoCertificate,
|
||||||
|
AllowCertificate,
|
||||||
|
RequireCertificate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Server.Kestrel.Https
|
||||||
|
{
|
||||||
|
public delegate bool ClientCertificateValidationCallback(
|
||||||
|
X509Certificate2 certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors);
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Https
|
||||||
public static class HttpsApplicationBuilderExtensions
|
public static class HttpsApplicationBuilderExtensions
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseKestrelHttps(this IApplicationBuilder app, X509Certificate2 cert)
|
public static IApplicationBuilder UseKestrelHttps(this IApplicationBuilder app, X509Certificate2 cert)
|
||||||
|
{
|
||||||
|
return app.UseKestrelHttps(new HttpsConnectionFilterOptions { ServerCertificate = cert});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IApplicationBuilder UseKestrelHttps(this IApplicationBuilder app, HttpsConnectionFilterOptions options)
|
||||||
{
|
{
|
||||||
var serverInfo = app.ServerFeatures.Get<IKestrelServerInformation>();
|
var serverInfo = app.ServerFeatures.Get<IKestrelServerInformation>();
|
||||||
|
|
||||||
|
|
@ -21,7 +26,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Https
|
||||||
|
|
||||||
var prevFilter = serverInfo.ConnectionFilter ?? new NoOpConnectionFilter();
|
var prevFilter = serverInfo.ConnectionFilter ?? new NoOpConnectionFilter();
|
||||||
|
|
||||||
serverInfo.ConnectionFilter = new HttpsConnectionFilter(cert, prevFilter);
|
serverInfo.ConnectionFilter = new HttpsConnectionFilter(options, prevFilter);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,37 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
|
using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
using Microsoft.AspNet.Http.Features.Internal;
|
||||||
using Microsoft.AspNet.Server.Kestrel.Filter;
|
using Microsoft.AspNet.Server.Kestrel.Filter;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Https
|
namespace Microsoft.AspNet.Server.Kestrel.Https
|
||||||
{
|
{
|
||||||
public class HttpsConnectionFilter : IConnectionFilter
|
public class HttpsConnectionFilter : IConnectionFilter
|
||||||
{
|
{
|
||||||
private readonly X509Certificate2 _cert;
|
private readonly X509Certificate2 _serverCert;
|
||||||
|
private readonly ClientCertificateMode _clientCertMode;
|
||||||
|
private readonly ClientCertificateValidationCallback _clientValidationCallback;
|
||||||
private readonly IConnectionFilter _previous;
|
private readonly IConnectionFilter _previous;
|
||||||
|
private X509Certificate2 _clientCert;
|
||||||
|
|
||||||
public HttpsConnectionFilter(X509Certificate2 cert, IConnectionFilter previous)
|
public HttpsConnectionFilter(HttpsConnectionFilterOptions options, IConnectionFilter previous)
|
||||||
{
|
{
|
||||||
if (cert == null)
|
if (options.ServerCertificate == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(cert));
|
throw new ArgumentNullException(nameof(options.ServerCertificate));
|
||||||
}
|
}
|
||||||
if (previous == null)
|
if (previous == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(previous));
|
throw new ArgumentNullException(nameof(previous));
|
||||||
}
|
}
|
||||||
|
|
||||||
_cert = cert;
|
_serverCert = options.ServerCertificate;
|
||||||
|
_clientCertMode = options.ClientCertificateMode;
|
||||||
|
_clientValidationCallback = options.ClientCertificateValidation;
|
||||||
_previous = previous;
|
_previous = previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,10 +43,68 @@ namespace Microsoft.AspNet.Server.Kestrel.Https
|
||||||
|
|
||||||
if (string.Equals(context.Address.Scheme, "https", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(context.Address.Scheme, "https", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var sslStream = new SslStream(context.Connection);
|
SslStream sslStream;
|
||||||
await sslStream.AuthenticateAsServerAsync(_cert);
|
if (_clientCertMode == ClientCertificateMode.NoCertificate)
|
||||||
|
{
|
||||||
|
sslStream = new SslStream(context.Connection);
|
||||||
|
await sslStream.AuthenticateAsServerAsync(_serverCert);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sslStream = new SslStream(context.Connection, leaveInnerStreamOpen: false,
|
||||||
|
userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) =>
|
||||||
|
{
|
||||||
|
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable))
|
||||||
|
{
|
||||||
|
return _clientCertMode != ClientCertificateMode.RequireCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (_clientValidationCallback == null)
|
||||||
|
{
|
||||||
|
if (sslPolicyErrors != SslPolicyErrors.None)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if DOTNET5_4
|
||||||
|
// conversion X509Certificate to X509Certificate2 not supported
|
||||||
|
// https://github.com/dotnet/corefx/issues/4510
|
||||||
|
X509Certificate2 certificate2 = null;
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
X509Certificate2 certificate2 = certificate as X509Certificate2 ??
|
||||||
|
new X509Certificate2(certificate);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
if (_clientValidationCallback != null)
|
||||||
|
{
|
||||||
|
if (!_clientValidationCallback(certificate2, chain, sslPolicyErrors))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_clientCert = certificate2;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
await sslStream.AuthenticateAsServerAsync(_serverCert, clientCertificateRequired: true,
|
||||||
|
enabledSslProtocols: SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
|
||||||
|
checkCertificateRevocation: false);
|
||||||
|
}
|
||||||
context.Connection = sslStream;
|
context.Connection = sslStream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PrepareRequest(IFeatureCollection features)
|
||||||
|
{
|
||||||
|
_previous.PrepareRequest(features);
|
||||||
|
|
||||||
|
if (_clientCert != null)
|
||||||
|
{
|
||||||
|
features.Set<ITlsConnectionFeature>(
|
||||||
|
new TlsConnectionFeature { ClientCertificate = _clientCert });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Server.Kestrel.Https
|
||||||
|
{
|
||||||
|
public class HttpsConnectionFilterOptions
|
||||||
|
{
|
||||||
|
public HttpsConnectionFilterOptions()
|
||||||
|
{
|
||||||
|
ClientCertificateMode = ClientCertificateMode.NoCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 ServerCertificate { get; set; }
|
||||||
|
public ClientCertificateMode ClientCertificateMode { get; set; }
|
||||||
|
public ClientCertificateValidationCallback ClientCertificateValidation { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Filter
|
namespace Microsoft.AspNet.Server.Kestrel.Filter
|
||||||
{
|
{
|
||||||
public class ConnectionFilterContext
|
public class ConnectionFilterContext
|
||||||
{
|
{
|
||||||
public ServerAddress Address { get; set; }
|
public ServerAddress Address { get; set; }
|
||||||
public Stream Connection { get; set; }
|
public Stream Connection { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Filter
|
namespace Microsoft.AspNet.Server.Kestrel.Filter
|
||||||
{
|
{
|
||||||
public interface IConnectionFilter
|
public interface IConnectionFilter
|
||||||
{
|
{
|
||||||
Task OnConnection(ConnectionFilterContext context);
|
Task OnConnection(ConnectionFilterContext context);
|
||||||
|
void PrepareRequest(IFeatureCollection features);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.Kestrel.Filter
|
namespace Microsoft.AspNet.Server.Kestrel.Filter
|
||||||
|
|
@ -12,5 +13,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
|
||||||
{
|
{
|
||||||
return TaskUtilities.CompletedTask;
|
return TaskUtilities.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PrepareRequest(IFeatureCollection features)
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
private Frame CreateFrame()
|
private Frame CreateFrame()
|
||||||
{
|
{
|
||||||
return new Frame(this, _remoteEndPoint, _localEndPoint);
|
return new Frame(this, _remoteEndPoint, _localEndPoint, ConnectionFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IConnectionControl.Pause()
|
void IConnectionControl.Pause()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Features;
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
using Microsoft.AspNet.Server.Kestrel.Filter;
|
||||||
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
@ -55,19 +56,22 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
|
|
||||||
private readonly IPEndPoint _localEndPoint;
|
private readonly IPEndPoint _localEndPoint;
|
||||||
private readonly IPEndPoint _remoteEndPoint;
|
private readonly IPEndPoint _remoteEndPoint;
|
||||||
|
private readonly IConnectionFilter _connectionFilter;
|
||||||
|
|
||||||
public Frame(ConnectionContext context)
|
public Frame(ConnectionContext context)
|
||||||
: this(context, remoteEndPoint: null, localEndPoint: null)
|
: this(context, remoteEndPoint: null, localEndPoint: null, connectionFilter: null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public Frame(ConnectionContext context,
|
public Frame(ConnectionContext context,
|
||||||
IPEndPoint remoteEndPoint,
|
IPEndPoint remoteEndPoint,
|
||||||
IPEndPoint localEndPoint)
|
IPEndPoint localEndPoint,
|
||||||
|
IConnectionFilter connectionFilter)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
_remoteEndPoint = remoteEndPoint;
|
_remoteEndPoint = remoteEndPoint;
|
||||||
_localEndPoint = localEndPoint;
|
_localEndPoint = localEndPoint;
|
||||||
|
_connectionFilter = connectionFilter;
|
||||||
|
|
||||||
FrameControl = this;
|
FrameControl = this;
|
||||||
Reset();
|
Reset();
|
||||||
|
|
@ -140,6 +144,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
httpConnectionFeature.IsLocal = false;
|
httpConnectionFeature.IsLocal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_connectionFilter?.PrepareRequest(this);
|
||||||
|
|
||||||
_requestAbortCts?.Dispose();
|
_requestAbortCts?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +278,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
// If _requestAbort is set, the connection has already been closed.
|
// If _requestAbort is set, the connection has already been closed.
|
||||||
if (!_requestAborted)
|
if (!_requestAborted)
|
||||||
{
|
{
|
||||||
await ProduceEnd();
|
await ProduceEnd();
|
||||||
|
|
||||||
// Finish reading the request body in case the app did not.
|
// Finish reading the request body in case the app did not.
|
||||||
await messageBody.Consume();
|
await messageBody.Consume();
|
||||||
|
|
@ -301,15 +307,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||||
// If _requestAborted is set, the connection has already been closed.
|
// If _requestAborted is set, the connection has already been closed.
|
||||||
if (!_requestAborted)
|
if (!_requestAborted)
|
||||||
{
|
{
|
||||||
// Inform client no more data will ever arrive
|
// Inform client no more data will ever arrive
|
||||||
ConnectionControl.End(ProduceEndType.SocketShutdownSend);
|
ConnectionControl.End(ProduceEndType.SocketShutdownSend);
|
||||||
|
|
||||||
// Wait for client to either disconnect or send unexpected data
|
// Wait for client to either disconnect or send unexpected data
|
||||||
await SocketInput;
|
await SocketInput;
|
||||||
|
|
||||||
// Dispose socket
|
// Dispose socket
|
||||||
ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
ConnectionControl.End(ProduceEndType.SocketDisconnect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
return _empty;
|
return _empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PrepareRequest(IFeatureCollection frame)
|
||||||
|
{}
|
||||||
|
|
||||||
public int BytesRead => _rewritingStream.BytesRead;
|
public int BytesRead => _rewritingStream.BytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,6 +113,9 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
|
|
||||||
context.Connection = new RewritingStream(oldConnection);
|
context.Connection = new RewritingStream(oldConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PrepareRequest(IFeatureCollection frame)
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RewritingStream : Stream
|
private class RewritingStream : Stream
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,16 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Server.Kestrel.Filter;
|
using Microsoft.AspNet.Server.Kestrel.Filter;
|
||||||
using Microsoft.AspNet.Server.Kestrel.Https;
|
using Microsoft.AspNet.Server.Kestrel.Https;
|
||||||
using Microsoft.AspNet.Testing.xunit;
|
using Microsoft.AspNet.Testing.xunit;
|
||||||
|
|
@ -55,19 +59,20 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
|
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var sereverAddress = "https://localhost:54321/";
|
var serverAddress = "https://localhost:54321/";
|
||||||
var serviceContext = new TestServiceContext()
|
var serviceContext = new TestServiceContext()
|
||||||
{
|
{
|
||||||
ConnectionFilter = new HttpsConnectionFilter(
|
ConnectionFilter = new HttpsConnectionFilter(
|
||||||
new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"),
|
new HttpsConnectionFilterOptions
|
||||||
|
{ ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword")},
|
||||||
new NoOpConnectionFilter())
|
new NoOpConnectionFilter())
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var server = new TestServer(App, serviceContext, sereverAddress))
|
using (var server = new TestServer(App, serviceContext, serverAddress))
|
||||||
{
|
{
|
||||||
using (var client = new HttpClient(handler))
|
using (var client = new HttpClient(handler))
|
||||||
{
|
{
|
||||||
var result = await client.PostAsync(sereverAddress, new FormUrlEncodedContent(new[] {
|
var result = await client.PostAsync(serverAddress, new FormUrlEncodedContent(new[] {
|
||||||
new KeyValuePair<string, string>("content", "Hello World?")
|
new KeyValuePair<string, string>("content", "Hello World?")
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -82,5 +87,189 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/aspnet/KestrelHttpServer/issues/240
|
||||||
|
// This test currently fails on mono because of an issue with SslStream.
|
||||||
|
[ConditionalFact]
|
||||||
|
[OSSkipCondition(OperatingSystems.Linux)]
|
||||||
|
[OSSkipCondition(OperatingSystems.MacOSX)]
|
||||||
|
public async Task RequireCertificateFailsWhenNoCertificate()
|
||||||
|
{
|
||||||
|
RemoteCertificateValidationCallback validationCallback =
|
||||||
|
(sender, cert, chain, sslPolicyErrors) => true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#if DNX451
|
||||||
|
var handler = new HttpClientHandler();
|
||||||
|
ServicePointManager.ServerCertificateValidationCallback += validationCallback;
|
||||||
|
#else
|
||||||
|
var handler = new WinHttpHandler();
|
||||||
|
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var serverAddress = "https://localhost:54321/";
|
||||||
|
var serviceContext = new TestServiceContext()
|
||||||
|
{
|
||||||
|
ConnectionFilter = new HttpsConnectionFilter(
|
||||||
|
new HttpsConnectionFilterOptions
|
||||||
|
{
|
||||||
|
ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"),
|
||||||
|
ClientCertificateMode = ClientCertificateMode.RequireCertificate
|
||||||
|
},
|
||||||
|
new NoOpConnectionFilter())
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var server = new TestServer(App, serviceContext, serverAddress))
|
||||||
|
{
|
||||||
|
using (var client = new HttpClient())
|
||||||
|
{
|
||||||
|
await Assert.ThrowsAnyAsync<Exception>(
|
||||||
|
() => client.GetAsync(serverAddress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
#if DNX451
|
||||||
|
ServicePointManager.ServerCertificateValidationCallback -= validationCallback;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/dotnet/corefx/issues/4512
|
||||||
|
// WinHttpHandler throws an Exception (ERROR_INTERNET_SECURE_FAILURE)
|
||||||
|
#if DNX451
|
||||||
|
|
||||||
|
// https://github.com/aspnet/KestrelHttpServer/issues/240
|
||||||
|
// This test currently fails on mono because of an issue with SslStream.
|
||||||
|
[ConditionalFact]
|
||||||
|
[OSSkipCondition(OperatingSystems.Linux)]
|
||||||
|
[OSSkipCondition(OperatingSystems.MacOSX)]
|
||||||
|
public async Task AllowCertificateContinuesWhenNoCertificate()
|
||||||
|
{
|
||||||
|
RemoteCertificateValidationCallback validationCallback =
|
||||||
|
(sender, cert, chain, sslPolicyErrors) => true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#if DNX451
|
||||||
|
var handler = new HttpClientHandler();
|
||||||
|
ServicePointManager.ServerCertificateValidationCallback += validationCallback;
|
||||||
|
#else
|
||||||
|
var handler = new WinHttpHandler();
|
||||||
|
handler.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var serverAddress = "https://localhost:54321/";
|
||||||
|
var serviceContext = new TestServiceContext()
|
||||||
|
{
|
||||||
|
ConnectionFilter = new HttpsConnectionFilter(
|
||||||
|
new HttpsConnectionFilterOptions
|
||||||
|
{
|
||||||
|
ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"),
|
||||||
|
ClientCertificateMode = ClientCertificateMode.AllowCertificate
|
||||||
|
},
|
||||||
|
new NoOpConnectionFilter())
|
||||||
|
};
|
||||||
|
|
||||||
|
RequestDelegate app = context =>
|
||||||
|
{
|
||||||
|
Assert.Equal(context.Features.Get<ITlsConnectionFeature>(), null);
|
||||||
|
return context.Response.WriteAsync("hello world");
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var server = new TestServer(app, serviceContext, serverAddress))
|
||||||
|
{
|
||||||
|
using (var client = new HttpClient())
|
||||||
|
{
|
||||||
|
var result = await client.GetAsync(serverAddress);
|
||||||
|
|
||||||
|
Assert.Equal("hello world", await result.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
#if DNX451
|
||||||
|
ServicePointManager.ServerCertificateValidationCallback -= validationCallback;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// https://github.com/dotnet/corefx/issues/4510
|
||||||
|
// Can't convert X509Certificate to X509Certificate2 in HttpsConnectionFilter
|
||||||
|
#if DNX451
|
||||||
|
|
||||||
|
// https://github.com/aspnet/KestrelHttpServer/issues/240
|
||||||
|
// This test currently fails on mono because of an issue with SslStream.
|
||||||
|
[ConditionalFact]
|
||||||
|
[OSSkipCondition(OperatingSystems.Linux)]
|
||||||
|
[OSSkipCondition(OperatingSystems.MacOSX)]
|
||||||
|
public async Task CertificatePassedToHttpContext()
|
||||||
|
{
|
||||||
|
RemoteCertificateValidationCallback validationCallback =
|
||||||
|
(sender, cert, chain, sslPolicyErrors) => true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#if DNX451
|
||||||
|
ServicePointManager.ServerCertificateValidationCallback += validationCallback;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var serverAddress = "https://localhost:54321/";
|
||||||
|
var serviceContext = new TestServiceContext()
|
||||||
|
{
|
||||||
|
ConnectionFilter = new HttpsConnectionFilter(
|
||||||
|
new HttpsConnectionFilterOptions
|
||||||
|
{
|
||||||
|
ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"),
|
||||||
|
ClientCertificateMode = ClientCertificateMode.RequireCertificate,
|
||||||
|
ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true
|
||||||
|
},
|
||||||
|
new NoOpConnectionFilter())
|
||||||
|
};
|
||||||
|
|
||||||
|
RequestDelegate app = context =>
|
||||||
|
{
|
||||||
|
var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
|
||||||
|
Assert.NotNull(tlsFeature);
|
||||||
|
Assert.NotNull(tlsFeature.ClientCertificate);
|
||||||
|
Assert.NotNull(context.Connection.ClientCertificate);
|
||||||
|
return context.Response.WriteAsync("hello world");
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var server = new TestServer(app, serviceContext, serverAddress))
|
||||||
|
{
|
||||||
|
// SslStream is used to ensure the certificate is actually passed to the server
|
||||||
|
// HttpClient might not send the certificate because it is invalid or it doesn't match any
|
||||||
|
// of the certificate authorities sent by the server in the SSL handshake.
|
||||||
|
using (var client = new TcpClient())
|
||||||
|
{
|
||||||
|
await client.ConnectAsync("127.0.0.1", 54321);
|
||||||
|
|
||||||
|
SslStream stream = new SslStream(client.GetStream(), false, (sender, certificate, chain, errors) => true,
|
||||||
|
(sender, host, certificates, certificate, issuers) => new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"));
|
||||||
|
await stream.AuthenticateAsClientAsync("localhost");
|
||||||
|
|
||||||
|
var request = Encoding.UTF8.GetBytes("GET / HTTP/1.0\r\n\r\n");
|
||||||
|
await stream.WriteAsync(request, 0, request.Length);
|
||||||
|
|
||||||
|
var reader = new StreamReader(stream);
|
||||||
|
var line = await reader.ReadLineAsync();
|
||||||
|
Assert.Equal("HTTP/1.0 200 OK", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
#if DNX451
|
||||||
|
ServicePointManager.ServerCertificateValidationCallback -= validationCallback;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue