Add SNI support #2357

This commit is contained in:
Chris Ross (ASP.NET) 2018-03-23 10:23:17 -07:00
parent 623c27ab01
commit 9ea2c50068
10 changed files with 323 additions and 48 deletions

View File

@ -7,39 +7,39 @@
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview2-15749</InternalAspNetCoreSdkPackageVersion>
<LibuvPackageVersion>1.10.0</LibuvPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.1.0-a-preview2-mpr-16443</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion>2.1.0-a-preview2-mpr-16443</MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.0-preview2-30478</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.1.0-preview2-30478</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.1.0-preview2-30478</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpFeaturesPackageVersion>2.1.0-preview2-30478</MicrosoftAspNetCoreHttpFeaturesPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>2.1.0-preview2-30478</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview2-30478</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.1.0-preview2-30478</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>
<MicrosoftExtensionsBuffersSourcesPackageVersion>2.1.0-a-preview2-mpr-16443</MicrosoftExtensionsBuffersSourcesPackageVersion>
<MicrosoftExtensionsBuffersTestingSourcesPackageVersion>2.1.0-a-preview2-mpr-16443</MicrosoftExtensionsBuffersTestingSourcesPackageVersion>
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsConfigurationBinderPackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.1.0-preview2-30478</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpFeaturesPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreHttpFeaturesPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.1.0-preview2-30554</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>
<MicrosoftExtensionsBuffersSourcesPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsBuffersSourcesPackageVersion>
<MicrosoftExtensionsBuffersTestingSourcesPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsBuffersTestingSourcesPackageVersion>
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsConfigurationBinderPackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.1.0-preview2-30554</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview3-26331-01</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>2.1.0-preview2-30478</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26403-06</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>2.1.0-preview2-30554</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
<SystemBuffersPackageVersion>4.5.0-preview3-26331-02</SystemBuffersPackageVersion>
<SystemIOPipelinesPackageVersion>4.5.0-preview3-26331-02</SystemIOPipelinesPackageVersion>
<SystemMemoryPackageVersion>4.5.0-preview3-26331-02</SystemMemoryPackageVersion>
<SystemNumericsVectorsPackageVersion>4.5.0-preview3-26331-02</SystemNumericsVectorsPackageVersion>
<SystemRuntimeCompilerServicesUnsafePackageVersion>4.5.0-preview3-26331-02</SystemRuntimeCompilerServicesUnsafePackageVersion>
<SystemSecurityCryptographyCngPackageVersion>4.5.0-preview3-26331-02</SystemSecurityCryptographyCngPackageVersion>
<SystemThreadingTasksExtensionsPackageVersion>4.5.0-preview3-26331-02</SystemThreadingTasksExtensionsPackageVersion>
<SystemBuffersPackageVersion>4.5.0-preview2-26403-05</SystemBuffersPackageVersion>
<SystemIOPipelinesPackageVersion>4.5.0-preview2-26403-05</SystemIOPipelinesPackageVersion>
<SystemMemoryPackageVersion>4.5.0-preview2-26403-05</SystemMemoryPackageVersion>
<SystemNumericsVectorsPackageVersion>4.5.0-preview2-26403-05</SystemNumericsVectorsPackageVersion>
<SystemRuntimeCompilerServicesUnsafePackageVersion>4.5.0-preview2-26403-05</SystemRuntimeCompilerServicesUnsafePackageVersion>
<SystemSecurityCryptographyCngPackageVersion>4.5.0-preview2-26403-05</SystemSecurityCryptographyCngPackageVersion>
<SystemThreadingTasksExtensionsPackageVersion>4.5.0-preview2-26403-05</SystemThreadingTasksExtensionsPackageVersion>
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
<XunitPackageVersion>2.3.1</XunitPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.4.0-beta.1.build3945</XunitRunnerVisualStudioPackageVersion>

View File

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@ -105,10 +106,23 @@ namespace SampleApp
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
.Configure()
.Endpoint(IPAddress.Loopback, basePort + 5)
.LocalhostEndpoint(basePort + 6)
.Endpoint(IPAddress.Loopback, basePort + 6)
.LocalhostEndpoint(basePort + 7)
.Load();
options

View File

@ -477,7 +477,7 @@
<data name="PositiveTimeSpanRequired1" xml:space="preserve">
<value>Value must be a positive TimeSpan.</value>
</data>
<data name="ServiceCertificateRequired" xml:space="preserve">
<data name="ServerCertificateRequired" xml:space="preserve">
<value>The server certificate parameter is required.</value>
</data>
<data name="BindingToDefaultAddresses" xml:space="preserve">

View File

@ -6,6 +6,7 @@ using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace Microsoft.AspNetCore.Server.Kestrel.Https
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
/// <summary>
/// <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>
/// 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>
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>
/// Specifies the client certificate requirements for a HTTPS connection. Defaults to <see cref="ClientCertificateMode.NoCertificate"/>.
/// </summary>

View File

@ -22,6 +22,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
private readonly HttpsConnectionAdapterOptions _options;
private readonly X509Certificate2 _serverCertificate;
private readonly Func<IFeatureCollection, string, X509Certificate2> _serverCertificateSelector;
private readonly ILogger _logger;
public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options)
@ -36,15 +38,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
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
_serverCertificate = options.ServerCertificate;
EnsureCertificateIsAllowedForServerAuth(_serverCertificate);
// If a selector is provided then ignore the cert, it may be a default cert.
if (_serverCertificateSelector != null)
{
// SslStream doesn't allow both.
_serverCertificate = null;
}
else
{
EnsureCertificateIsAllowedForServerAuth(_serverCertificate);
}
_options = options;
_logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionAdapter));
@ -115,9 +126,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
try
{
#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()
{
ServerCertificate = _serverCertificate,
ServerCertificateSelectionCallback = selector,
ClientCertificateRequired = certificateRequired,
EnabledSslProtocols = _options.SslProtocols,
CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
@ -137,7 +165,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None);
#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);
#endif
}

View File

@ -236,7 +236,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
// EndpointDefaults or configureEndpoint may have added an https adapter.
if (https && !listenOptions.ConnectionAdapters.Any(f => f.IsHttps))
{
if (httpsOptions.ServerCertificate == null)
if (httpsOptions.ServerCertificate == null && httpsOptions.ServerCertificateSelector == null)
{
throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
}

View File

@ -179,7 +179,7 @@ namespace Microsoft.AspNetCore.Hosting
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
configureOptions(options);
if (options.ServerCertificate == null)
if (options.ServerCertificate == null && options.ServerCertificateSelector == null)
{
throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
}
@ -192,7 +192,7 @@ namespace Microsoft.AspNetCore.Hosting
var options = new HttpsConnectionAdapterOptions();
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
if (options.ServerCertificate == null)
if (options.ServerCertificate == null && options.ServerCertificateSelector == null)
{
return false;
}

View File

@ -1693,16 +1693,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// <summary>
/// The server certificate parameter is required.
/// </summary>
internal static string ServiceCertificateRequired
internal static string ServerCertificateRequired
{
get => GetString("ServiceCertificateRequired");
get => GetString("ServerCertificateRequired");
}
/// <summary>
/// The server certificate parameter is required.
/// </summary>
internal static string FormatServiceCertificateRequired()
=> GetString("ServiceCertificateRequired");
internal static string FormatServerCertificateRequired()
=> GetString("ServerCertificateRequired");
/// <summary>
/// No listening endpoints were configured. Binding to {address0} and {address1} by default.

View File

@ -26,7 +26,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
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;
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]
public async Task CertificatePassedToHttpContext()

View File

@ -17,5 +17,10 @@ namespace Microsoft.AspNetCore.Testing
{
return new X509Certificate2(TestCertificatePath, "testPassword");
}
public static X509Certificate2 GetTestCertificate(string certName)
{
return new X509Certificate2(GetCertPath(certName), "testPassword");
}
}
}