Improve based on pull request feedback

This commit is contained in:
Master T 2015-11-16 22:04:27 +01:00
parent bed8c67181
commit 8e910baf04
10 changed files with 61 additions and 82 deletions

View File

@ -1,11 +0,0 @@
// 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);
}

View File

@ -14,28 +14,25 @@ namespace Microsoft.AspNet.Server.Kestrel.Https
{ {
public class HttpsConnectionFilter : IConnectionFilter public class HttpsConnectionFilter : IConnectionFilter
{ {
private readonly X509Certificate2 _serverCert; private readonly HttpsConnectionFilterOptions _options;
private readonly ClientCertificateMode _clientCertMode;
private readonly ClientCertificateValidationCallback _clientValidationCallback;
private readonly IConnectionFilter _previous; private readonly IConnectionFilter _previous;
private readonly SslProtocols _sslProtocols;
private X509Certificate2 _clientCert;
public HttpsConnectionFilter(HttpsConnectionFilterOptions options, IConnectionFilter previous) public HttpsConnectionFilter(HttpsConnectionFilterOptions options, IConnectionFilter previous)
{ {
if (options.ServerCertificate == null) if (options == null)
{ {
throw new ArgumentNullException(nameof(options.ServerCertificate)); throw new ArgumentNullException(nameof(options));
} }
if (previous == null) if (previous == null)
{ {
throw new ArgumentNullException(nameof(previous)); throw new ArgumentNullException(nameof(previous));
} }
if (options.ServerCertificate == null)
{
throw new ArgumentException("The server certificate parameter is required.");
}
_serverCert = options.ServerCertificate; _options = options;
_clientCertMode = options.ClientCertificateMode;
_clientValidationCallback = options.ClientCertificateValidation;
_sslProtocols = options.SslProtocols;
_previous = previous; _previous = previous;
} }
@ -45,68 +42,76 @@ namespace Microsoft.AspNet.Server.Kestrel.Https
if (string.Equals(context.Address.Scheme, "https", StringComparison.OrdinalIgnoreCase)) if (string.Equals(context.Address.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{ {
X509Certificate2 clientCertificate = null;
SslStream sslStream; SslStream sslStream;
if (_clientCertMode == ClientCertificateMode.NoCertificate) if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate)
{ {
sslStream = new SslStream(context.Connection); sslStream = new SslStream(context.Connection);
await sslStream.AuthenticateAsServerAsync(_serverCert, clientCertificateRequired: false, await sslStream.AuthenticateAsServerAsync(_options.ServerCertificate, clientCertificateRequired: false,
enabledSslProtocols: _sslProtocols, checkCertificateRevocation: false); enabledSslProtocols: _options.SslProtocols, checkCertificateRevocation: _options.CheckCertificateRevocation);
} }
else else
{ {
sslStream = new SslStream(context.Connection, leaveInnerStreamOpen: false, sslStream = new SslStream(context.Connection, leaveInnerStreamOpen: false,
userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) =>
{ {
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable)) Console.WriteLine("callback type: " + (certificate is X509Certificate2));
if (certificate == null)
{ {
return _clientCertMode != ClientCertificateMode.RequireCertificate; return _options.ClientCertificateMode != ClientCertificateMode.RequireCertificate;
} }
if (_options.ClientCertificateValidation == null)
if (_clientValidationCallback == null)
{ {
if (sslPolicyErrors != SslPolicyErrors.None) if (sslPolicyErrors != SslPolicyErrors.None)
{ {
return false; 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 X509Certificate2 certificate2 = certificate as X509Certificate2;
if (_clientValidationCallback != null) if (certificate2 == null)
{ {
if (!_clientValidationCallback(certificate2, chain, sslPolicyErrors)) #if DOTNET5_4
// conversion X509Certificate to X509Certificate2 not supported
// https://github.com/dotnet/corefx/issues/4510
return false;
#else
certificate2 = new X509Certificate2(certificate);
#endif
}
if (_options.ClientCertificateValidation != null)
{
if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors))
{ {
return false; return false;
} }
} }
_clientCert = certificate2; clientCertificate = certificate2;
return true; return true;
}); });
await sslStream.AuthenticateAsServerAsync(_serverCert, clientCertificateRequired: true, await sslStream.AuthenticateAsServerAsync(_options.ServerCertificate, clientCertificateRequired: true,
enabledSslProtocols: _sslProtocols, checkCertificateRevocation: false); enabledSslProtocols: _options.SslProtocols, checkCertificateRevocation: _options.CheckCertificateRevocation);
Console.WriteLine("remote type: " + (sslStream.RemoteCertificate is X509Certificate2));
} }
var previousPrepareRequest = context.PrepareRequest;
context.PrepareRequest = features =>
{
previousPrepareRequest?.Invoke(features);
if (clientCertificate != null)
{
features.Set<ITlsConnectionFeature>(
new TlsConnectionFeature {ClientCertificate = clientCertificate});
}
features.Get<IHttpRequestFeature>().Scheme = "https";
};
context.Connection = sslStream; context.Connection = sslStream;
} }
} }
public void PrepareRequest(IFeatureCollection features)
{
_previous.PrepareRequest(features);
if (_clientCert != null)
{
features.Set<ITlsConnectionFeature>(
new TlsConnectionFeature { ClientCertificate = _clientCert });
}
}
} }
} }

View File

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@ -16,7 +18,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Https
public X509Certificate2 ServerCertificate { get; set; } public X509Certificate2 ServerCertificate { get; set; }
public ClientCertificateMode ClientCertificateMode { get; set; } public ClientCertificateMode ClientCertificateMode { get; set; }
public ClientCertificateValidationCallback ClientCertificateValidation { get; set; } public Func<X509Certificate2, X509Chain, SslPolicyErrors, bool> ClientCertificateValidation { get; set; }
public SslProtocols SslProtocols { get; set; } public SslProtocols SslProtocols { get; set; }
public bool CheckCertificateRevocation { get; set; }
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.IO;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
@ -10,5 +11,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
{ {
public ServerAddress Address { get; set; } public ServerAddress Address { get; set; }
public Stream Connection { get; set; } public Stream Connection { get; set; }
public Action<IFeatureCollection> PrepareRequest { get; set; }
} }
} }

View File

@ -9,6 +9,5 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
public interface IConnectionFilter public interface IConnectionFilter
{ {
Task OnConnection(ConnectionFilterContext context); Task OnConnection(ConnectionFilterContext context);
void PrepareRequest(IFeatureCollection features);
} }
} }

View File

@ -13,8 +13,5 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
{ {
return TaskUtilities.CompletedTask; return TaskUtilities.CompletedTask;
} }
public void PrepareRequest(IFeatureCollection features)
{}
} }
} }

View File

@ -177,7 +177,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private Frame CreateFrame() private Frame CreateFrame()
{ {
return new Frame(this, _remoteEndPoint, _localEndPoint, ConnectionFilter); return new Frame(this, _remoteEndPoint, _localEndPoint, _filterContext?.PrepareRequest);
} }
void IConnectionControl.Pause() void IConnectionControl.Pause()

View File

@ -56,22 +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; private readonly Action<IFeatureCollection> _prepareRequest;
public Frame(ConnectionContext context) public Frame(ConnectionContext context)
: this(context, remoteEndPoint: null, localEndPoint: null, connectionFilter: null) : this(context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null)
{ {
} }
public Frame(ConnectionContext context, public Frame(ConnectionContext context,
IPEndPoint remoteEndPoint, IPEndPoint remoteEndPoint,
IPEndPoint localEndPoint, IPEndPoint localEndPoint,
IConnectionFilter connectionFilter) Action<IFeatureCollection> prepareRequest)
: base(context) : base(context)
{ {
_remoteEndPoint = remoteEndPoint; _remoteEndPoint = remoteEndPoint;
_localEndPoint = localEndPoint; _localEndPoint = localEndPoint;
_connectionFilter = connectionFilter; _prepareRequest = prepareRequest;
FrameControl = this; FrameControl = this;
Reset(); Reset();

View File

@ -95,9 +95,6 @@ namespace Microsoft.AspNet.Server.KestrelTests
return _empty; return _empty;
} }
public void PrepareRequest(IFeatureCollection frame)
{}
public int BytesRead => _rewritingStream.BytesRead; public int BytesRead => _rewritingStream.BytesRead;
} }
@ -113,9 +110,6 @@ 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

View File

@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
using (var server = new TestServer(App, serviceContext, serverAddress)) using (var server = new TestServer(App, serviceContext, serverAddress))
{ {
using (var client = new HttpClient()) using (var client = new HttpClient(handler))
{ {
await Assert.ThrowsAnyAsync<Exception>( await Assert.ThrowsAnyAsync<Exception>(
() => client.GetAsync(serverAddress)); () => client.GetAsync(serverAddress));
@ -137,10 +137,6 @@ namespace Microsoft.AspNet.Server.KestrelTests
} }
} }
// https://github.com/dotnet/corefx/issues/4512
// WinHttpHandler throws an Exception (ERROR_INTERNET_SECURE_FAILURE)
#if DNX451
// https://github.com/aspnet/KestrelHttpServer/issues/240 // https://github.com/aspnet/KestrelHttpServer/issues/240
// This test currently fails on mono because of an issue with SslStream. // This test currently fails on mono because of an issue with SslStream.
[ConditionalFact] [ConditionalFact]
@ -181,7 +177,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
using (var server = new TestServer(app, serviceContext, serverAddress)) using (var server = new TestServer(app, serviceContext, serverAddress))
{ {
using (var client = new HttpClient()) using (var client = new HttpClient(handler))
{ {
var result = await client.GetAsync(serverAddress); var result = await client.GetAsync(serverAddress);
@ -196,11 +192,6 @@ namespace Microsoft.AspNet.Server.KestrelTests
#endif #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 // https://github.com/aspnet/KestrelHttpServer/issues/240
// This test currently fails on mono because of an issue with SslStream. // This test currently fails on mono because of an issue with SslStream.
@ -269,7 +260,6 @@ namespace Microsoft.AspNet.Server.KestrelTests
#endif #endif
} }
} }
#endif
} }
} }