Merge branch 'release/2.1' into dev
This commit is contained in:
commit
953496a970
|
|
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -105,10 +106,23 @@ namespace SampleApp
|
||||||
listenOptions.UseHttps(StoreName.My, "localhost", allowInvalid: true);
|
listenOptions.UseHttps(StoreName.My, "localhost", allowInvalid: true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
options.ListenAnyIP(basePort + 5, listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.UseHttps(httpsOptions =>
|
||||||
|
{
|
||||||
|
var localhostCert = CertificateLoader.LoadFromStoreCert("localhost", "My", StoreLocation.CurrentUser, allowInvalid: true);
|
||||||
|
httpsOptions.ServerCertificateSelector = (features, name) =>
|
||||||
|
{
|
||||||
|
// Here you would check the name, select an appropriate cert, and provide a fallback or fail for null names.
|
||||||
|
return localhostCert;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
options
|
options
|
||||||
.Configure()
|
.Configure()
|
||||||
.Endpoint(IPAddress.Loopback, basePort + 5)
|
.Endpoint(IPAddress.Loopback, basePort + 6)
|
||||||
.LocalhostEndpoint(basePort + 6)
|
.LocalhostEndpoint(basePort + 7)
|
||||||
.Load();
|
.Load();
|
||||||
|
|
||||||
options
|
options
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@
|
||||||
<data name="PositiveTimeSpanRequired1" xml:space="preserve">
|
<data name="PositiveTimeSpanRequired1" xml:space="preserve">
|
||||||
<value>Value must be a positive TimeSpan.</value>
|
<value>Value must be a positive TimeSpan.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ServiceCertificateRequired" xml:space="preserve">
|
<data name="ServerCertificateRequired" xml:space="preserve">
|
||||||
<value>The server certificate parameter is required.</value>
|
<value>The server certificate parameter is required.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BindingToDefaultAddresses" xml:space="preserve">
|
<data name="BindingToDefaultAddresses" xml:space="preserve">
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Net.Security;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
||||||
|
|
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Specifies the server certificate used to authenticate HTTPS connections.
|
/// Specifies the server certificate used to authenticate HTTPS connections. This is ignored if ServerCertificateSelector is set.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// If the server certificate has an Extended Key Usage extension, the usages must include Server Authentication (OID 1.3.6.1.5.5.7.3.1).
|
/// If the server certificate has an Extended Key Usage extension, the usages must include Server Authentication (OID 1.3.6.1.5.5.7.3.1).
|
||||||
|
|
@ -37,6 +38,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public X509Certificate2 ServerCertificate { get; set; }
|
public X509Certificate2 ServerCertificate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// A callback that will be invoked to dynamically select a server certificate. This is higher priority than ServerCertificate.
|
||||||
|
/// If SNI is not avialable then the name parameter will be null.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If the server certificate has an Extended Key Usage extension, the usages must include Server Authentication (OID 1.3.6.1.5.5.7.3.1).
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public Func<IFeatureCollection, string, X509Certificate2> ServerCertificateSelector { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the client certificate requirements for a HTTPS connection. Defaults to <see cref="ClientCertificateMode.NoCertificate"/>.
|
/// Specifies the client certificate requirements for a HTTPS connection. Defaults to <see cref="ClientCertificateMode.NoCertificate"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
|
|
||||||
private readonly HttpsConnectionAdapterOptions _options;
|
private readonly HttpsConnectionAdapterOptions _options;
|
||||||
private readonly X509Certificate2 _serverCertificate;
|
private readonly X509Certificate2 _serverCertificate;
|
||||||
|
private readonly Func<IFeatureCollection, string, X509Certificate2> _serverCertificateSelector;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options)
|
public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options)
|
||||||
|
|
@ -36,15 +38,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
throw new ArgumentNullException(nameof(options));
|
throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.ServerCertificate == null)
|
// capture the certificate now so it can't be switched after validation
|
||||||
|
_serverCertificate = options.ServerCertificate;
|
||||||
|
_serverCertificateSelector = options.ServerCertificateSelector;
|
||||||
|
if (_serverCertificate == null && _serverCertificateSelector == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(CoreStrings.ServiceCertificateRequired, nameof(options));
|
throw new ArgumentException(CoreStrings.ServerCertificateRequired, nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
// capture the certificate now so it can be switched after validation
|
// If a selector is provided then ignore the cert, it may be a default cert.
|
||||||
_serverCertificate = options.ServerCertificate;
|
if (_serverCertificateSelector != null)
|
||||||
|
{
|
||||||
EnsureCertificateIsAllowedForServerAuth(_serverCertificate);
|
// SslStream doesn't allow both.
|
||||||
|
_serverCertificate = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EnsureCertificateIsAllowedForServerAuth(_serverCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
_options = options;
|
_options = options;
|
||||||
_logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionAdapter));
|
_logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionAdapter));
|
||||||
|
|
@ -115,9 +126,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if NETCOREAPP2_1
|
#if NETCOREAPP2_1
|
||||||
|
// Adapt to the SslStream signature
|
||||||
|
ServerCertificateSelectionCallback selector = null;
|
||||||
|
if (_serverCertificateSelector != null)
|
||||||
|
{
|
||||||
|
selector = (sender, name) =>
|
||||||
|
{
|
||||||
|
context.Features.Set(sslStream);
|
||||||
|
var cert = _serverCertificateSelector(context.Features, name);
|
||||||
|
if (cert != null)
|
||||||
|
{
|
||||||
|
EnsureCertificateIsAllowedForServerAuth(cert);
|
||||||
|
}
|
||||||
|
return cert;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var sslOptions = new SslServerAuthenticationOptions()
|
var sslOptions = new SslServerAuthenticationOptions()
|
||||||
{
|
{
|
||||||
ServerCertificate = _serverCertificate,
|
ServerCertificate = _serverCertificate,
|
||||||
|
ServerCertificateSelectionCallback = selector,
|
||||||
ClientCertificateRequired = certificateRequired,
|
ClientCertificateRequired = certificateRequired,
|
||||||
EnabledSslProtocols = _options.SslProtocols,
|
EnabledSslProtocols = _options.SslProtocols,
|
||||||
CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
|
CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
|
||||||
|
|
@ -137,7 +165,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
|
|
||||||
await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None);
|
await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None);
|
||||||
#else
|
#else
|
||||||
await sslStream.AuthenticateAsServerAsync(_serverCertificate, certificateRequired,
|
var serverCert = _serverCertificate;
|
||||||
|
if (_serverCertificateSelector != null)
|
||||||
|
{
|
||||||
|
context.Features.Set(sslStream);
|
||||||
|
serverCert = _serverCertificateSelector(context.Features, null);
|
||||||
|
if (serverCert != null)
|
||||||
|
{
|
||||||
|
EnsureCertificateIsAllowedForServerAuth(serverCert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await sslStream.AuthenticateAsServerAsync(serverCert, certificateRequired,
|
||||||
_options.SslProtocols, _options.CheckCertificateRevocation);
|
_options.SslProtocols, _options.CheckCertificateRevocation);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
||||||
// EndpointDefaults or configureEndpoint may have added an https adapter.
|
// EndpointDefaults or configureEndpoint may have added an https adapter.
|
||||||
if (https && !listenOptions.ConnectionAdapters.Any(f => f.IsHttps))
|
if (https && !listenOptions.ConnectionAdapters.Any(f => f.IsHttps))
|
||||||
{
|
{
|
||||||
if (httpsOptions.ServerCertificate == null)
|
if (httpsOptions.ServerCertificate == null && httpsOptions.ServerCertificateSelector == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
|
throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
|
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
|
||||||
configureOptions(options);
|
configureOptions(options);
|
||||||
|
|
||||||
if (options.ServerCertificate == null)
|
if (options.ServerCertificate == null && options.ServerCertificateSelector == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
|
throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +192,7 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
var options = new HttpsConnectionAdapterOptions();
|
var options = new HttpsConnectionAdapterOptions();
|
||||||
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
|
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
|
||||||
|
|
||||||
if (options.ServerCertificate == null)
|
if (options.ServerCertificate == null && options.ServerCertificateSelector == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1693,16 +1693,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The server certificate parameter is required.
|
/// The server certificate parameter is required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string ServiceCertificateRequired
|
internal static string ServerCertificateRequired
|
||||||
{
|
{
|
||||||
get => GetString("ServiceCertificateRequired");
|
get => GetString("ServerCertificateRequired");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The server certificate parameter is required.
|
/// The server certificate parameter is required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatServiceCertificateRequired()
|
internal static string FormatServerCertificateRequired()
|
||||||
=> GetString("ServiceCertificateRequired");
|
=> GetString("ServerCertificateRequired");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No listening endpoints were configured. Binding to {address0} and {address1} by default.
|
/// No listening endpoints were configured. Binding to {address0} and {address1} by default.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
{
|
{
|
||||||
public class HttpsConnectionAdapterTests
|
public class HttpsConnectionAdapterTests
|
||||||
{
|
{
|
||||||
private static X509Certificate2 _x509Certificate2 = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
|
private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate();
|
||||||
|
private static X509Certificate2 _x509Certificate2NoExt = TestResources.GetTestCertificate("no_extensions.pfx");
|
||||||
private readonly ITestOutputHelper _output;
|
private readonly ITestOutputHelper _output;
|
||||||
|
|
||||||
public HttpsConnectionAdapterTests(ITestOutputHelper output)
|
public HttpsConnectionAdapterTests(ITestOutputHelper output)
|
||||||
|
|
@ -148,6 +149,211 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UsesProvidedServerCertificateSelector()
|
||||||
|
{
|
||||||
|
var selectorCalled = 0;
|
||||||
|
var serviceContext = new TestServiceContext();
|
||||||
|
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||||
|
{
|
||||||
|
ConnectionAdapters =
|
||||||
|
{
|
||||||
|
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||||
|
{
|
||||||
|
ServerCertificateSelector = (features, name) =>
|
||||||
|
{
|
||||||
|
Assert.NotNull(features);
|
||||||
|
Assert.NotNull(features.Get<SslStream>());
|
||||||
|
#if NETCOREAPP2_1
|
||||||
|
Assert.Equal("localhost", name);
|
||||||
|
#else
|
||||||
|
Assert.Null(name);
|
||||||
|
#endif
|
||||||
|
selectorCalled++;
|
||||||
|
return _x509Certificate2;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using (var server = new TestServer(context => Task.CompletedTask, serviceContext, listenOptions))
|
||||||
|
{
|
||||||
|
using (var client = new TcpClient())
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
var stream = await OpenSslStream(client, server);
|
||||||
|
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
|
||||||
|
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
|
||||||
|
Assert.Equal(1, selectorCalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UsesProvidedServerCertificateSelectorEachTime()
|
||||||
|
{
|
||||||
|
var selectorCalled = 0;
|
||||||
|
var serviceContext = new TestServiceContext();
|
||||||
|
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||||
|
{
|
||||||
|
ConnectionAdapters =
|
||||||
|
{
|
||||||
|
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||||
|
{
|
||||||
|
ServerCertificateSelector = (features, name) =>
|
||||||
|
{
|
||||||
|
Assert.NotNull(features);
|
||||||
|
Assert.NotNull(features.Get<SslStream>());
|
||||||
|
#if NETCOREAPP2_1
|
||||||
|
Assert.Equal("localhost", name);
|
||||||
|
#else
|
||||||
|
Assert.Null(name);
|
||||||
|
#endif
|
||||||
|
selectorCalled++;
|
||||||
|
if (selectorCalled == 1)
|
||||||
|
{
|
||||||
|
return _x509Certificate2;
|
||||||
|
}
|
||||||
|
return _x509Certificate2NoExt;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using (var server = new TestServer(context => Task.CompletedTask, serviceContext, listenOptions))
|
||||||
|
{
|
||||||
|
using (var client = new TcpClient())
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
var stream = await OpenSslStream(client, server);
|
||||||
|
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
|
||||||
|
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
|
||||||
|
Assert.Equal(1, selectorCalled);
|
||||||
|
}
|
||||||
|
using (var client = new TcpClient())
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
var stream = await OpenSslStream(client, server);
|
||||||
|
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
|
||||||
|
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2NoExt));
|
||||||
|
Assert.Equal(2, selectorCalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UsesProvidedServerCertificateSelectorValidatesEkus()
|
||||||
|
{
|
||||||
|
var selectorCalled = 0;
|
||||||
|
var serviceContext = new TestServiceContext();
|
||||||
|
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||||
|
{
|
||||||
|
ConnectionAdapters =
|
||||||
|
{
|
||||||
|
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||||
|
{
|
||||||
|
ServerCertificateSelector = (features, name) =>
|
||||||
|
{
|
||||||
|
selectorCalled++;
|
||||||
|
return TestResources.GetTestCertificate("eku.code_signing.pfx");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using (var server = new TestServer(context => Task.CompletedTask, serviceContext, listenOptions))
|
||||||
|
{
|
||||||
|
using (var client = new TcpClient())
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
var stream = await OpenSslStream(client, server);
|
||||||
|
await Assert.ThrowsAsync<IOException>(() =>
|
||||||
|
stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false));
|
||||||
|
Assert.Equal(1, selectorCalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UsesProvidedServerCertificateSelectorOverridesServerCertificate()
|
||||||
|
{
|
||||||
|
var selectorCalled = 0;
|
||||||
|
var serviceContext = new TestServiceContext();
|
||||||
|
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||||
|
{
|
||||||
|
ConnectionAdapters =
|
||||||
|
{
|
||||||
|
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||||
|
{
|
||||||
|
ServerCertificate = _x509Certificate2NoExt,
|
||||||
|
ServerCertificateSelector = (features, name) =>
|
||||||
|
{
|
||||||
|
Assert.NotNull(features);
|
||||||
|
Assert.NotNull(features.Get<SslStream>());
|
||||||
|
#if NETCOREAPP2_1
|
||||||
|
Assert.Equal("localhost", name);
|
||||||
|
#else
|
||||||
|
Assert.Null(name);
|
||||||
|
#endif
|
||||||
|
selectorCalled++;
|
||||||
|
return _x509Certificate2;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using (var server = new TestServer(context => Task.CompletedTask, serviceContext, listenOptions))
|
||||||
|
{
|
||||||
|
using (var client = new TcpClient())
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
var stream = await OpenSslStream(client, server);
|
||||||
|
await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false);
|
||||||
|
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
|
||||||
|
Assert.Equal(1, selectorCalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UsesProvidedServerCertificateSelectorFailsIfYouReturnNull()
|
||||||
|
{
|
||||||
|
var selectorCalled = 0;
|
||||||
|
var serviceContext = new TestServiceContext();
|
||||||
|
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||||
|
{
|
||||||
|
ConnectionAdapters =
|
||||||
|
{
|
||||||
|
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions
|
||||||
|
{
|
||||||
|
ServerCertificateSelector = (features, name) =>
|
||||||
|
{
|
||||||
|
selectorCalled++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using (var server = new TestServer(context => Task.CompletedTask, serviceContext, listenOptions))
|
||||||
|
{
|
||||||
|
using (var client = new TcpClient())
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
var stream = await OpenSslStream(client, server);
|
||||||
|
await Assert.ThrowsAsync<IOException>(() =>
|
||||||
|
stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false));
|
||||||
|
Assert.Equal(1, selectorCalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CertificatePassedToHttpContext()
|
public async Task CertificatePassedToHttpContext()
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,10 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
{
|
{
|
||||||
return new X509Certificate2(TestCertificatePath, "testPassword");
|
return new X509Certificate2(TestCertificatePath, "testPassword");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static X509Certificate2 GetTestCertificate(string certName)
|
||||||
|
{
|
||||||
|
return new X509Certificate2(GetCertPath(certName), "testPassword");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue