Add async ServerOptionsSelectionCallback UseHttps overload (#25390)
This commit is contained in:
parent
071a539ae5
commit
04f23ecfc4
|
|
@ -6,7 +6,6 @@ using System.IO;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -209,7 +208,8 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configure Kestrel to use HTTPS.
|
/// Configure Kestrel to use HTTPS. This does not use default certificates or other defaults specified via config or
|
||||||
|
/// <see cref="KestrelServerOptions.ConfigureHttpsDefaults(Action{HttpsConnectionAdapterOptions})"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
|
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
|
||||||
/// <param name="httpsOptions">Options to configure HTTPS.</param>
|
/// <param name="httpsOptions">Options to configure HTTPS.</param>
|
||||||
|
|
@ -230,12 +230,44 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
return listenOptions;
|
return listenOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configure Kestrel to use HTTPS. This does not use default certificates or other defaults specified via config or
|
||||||
|
/// <see cref="KestrelServerOptions.ConfigureHttpsDefaults(Action{HttpsConnectionAdapterOptions})"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
|
||||||
|
/// <param name="serverOptionsSelectionCallback">Callback to configure HTTPS options.</param>
|
||||||
|
/// <param name="state">State for the <paramref name="serverOptionsSelectionCallback"/>.</param>
|
||||||
|
/// <returns>The <see cref="ListenOptions"/>.</returns>
|
||||||
|
public static ListenOptions UseHttps(this ListenOptions listenOptions, ServerOptionsSelectionCallback serverOptionsSelectionCallback, object state)
|
||||||
|
{
|
||||||
|
return listenOptions.UseHttps(serverOptionsSelectionCallback, state, HttpsConnectionAdapterOptions.DefaultHandshakeTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configure Kestrel to use HTTPS. This does not use default certificates or other defaults specified via config or
|
||||||
|
/// <see cref="KestrelServerOptions.ConfigureHttpsDefaults(Action{HttpsConnectionAdapterOptions})"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
|
||||||
|
/// <param name="serverOptionsSelectionCallback">Callback to configure HTTPS options.</param>
|
||||||
|
/// <param name="state">State for the <paramref name="serverOptionsSelectionCallback"/>.</param>
|
||||||
|
/// <param name="handshakeTimeout">Specifies the maximum amount of time allowed for the TLS/SSL handshake. This must be positive and finite.</param>
|
||||||
|
/// <returns>The <see cref="ListenOptions"/>.</returns>
|
||||||
|
public static ListenOptions UseHttps(this ListenOptions listenOptions, ServerOptionsSelectionCallback serverOptionsSelectionCallback, object state, TimeSpan handshakeTimeout)
|
||||||
|
{
|
||||||
|
// HttpsOptionsCallback is an internal delegate that is just the ServerOptionsSelectionCallback + a ConnectionContext parameter.
|
||||||
|
// Given that ConnectionContext will eventually be replaced by System.Net.Connections, it doesn't make much sense to make the HttpsOptionsCallback delegate public.
|
||||||
|
HttpsOptionsCallback adaptedCallback = (connection, stream, clientHelloInfo, state, cancellationToken) =>
|
||||||
|
serverOptionsSelectionCallback(stream, clientHelloInfo, state, cancellationToken);
|
||||||
|
|
||||||
|
return listenOptions.UseHttps(adaptedCallback, state, handshakeTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configure Kestrel to use HTTPS.
|
/// Configure Kestrel to use HTTPS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
|
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
|
||||||
/// <param name="httpsOptionsCallback">Callback to configure HTTPS options.</param>
|
/// <param name="httpsOptionsCallback">Callback to configure HTTPS options.</param>
|
||||||
/// <param name="state">State for the <see cref="ServerOptionsSelectionCallback" />.</param>
|
/// <param name="state">State for the <paramref name="httpsOptionsCallback"/>.</param>
|
||||||
/// <param name="handshakeTimeout">Specifies the maximum amount of time allowed for the TLS/SSL handshake. This must be positive and finite.</param>
|
/// <param name="handshakeTimeout">Specifies the maximum amount of time allowed for the TLS/SSL handshake. This must be positive and finite.</param>
|
||||||
/// <returns>The <see cref="ListenOptions"/>.</returns>
|
/// <returns>The <see cref="ListenOptions"/>.</returns>
|
||||||
internal static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsOptionsCallback httpsOptionsCallback, object state, TimeSpan handshakeTimeout)
|
internal static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsOptionsCallback httpsOptionsCallback, object state, TimeSpan handshakeTimeout)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
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.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -109,15 +110,21 @@ namespace SampleApp
|
||||||
|
|
||||||
options.ListenAnyIP(basePort + 5, listenOptions =>
|
options.ListenAnyIP(basePort + 5, listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(httpsOptions =>
|
var localhostCert = CertificateLoader.LoadFromStoreCert("localhost", "My", StoreLocation.CurrentUser, allowInvalid: true);
|
||||||
|
|
||||||
|
listenOptions.UseHttps((stream, clientHelloInfo, state, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var localhostCert = CertificateLoader.LoadFromStoreCert("localhost", "My", StoreLocation.CurrentUser, allowInvalid: true);
|
// Here you would check the name, select an appropriate cert, and provide a fallback or fail for null names.
|
||||||
httpsOptions.ServerCertificateSelector = (features, name) =>
|
if (clientHelloInfo.ServerName != null && clientHelloInfo.ServerName != "localhost")
|
||||||
{
|
{
|
||||||
// Here you would check the name, select an appropriate cert, and provide a fallback or fail for null names.
|
throw new AuthenticationException($"The endpoint is not configured for sever name '{clientHelloInfo.ServerName}'.");
|
||||||
return localhostCert;
|
}
|
||||||
};
|
|
||||||
});
|
return new ValueTask<SslServerAuthenticationOptions>(new SslServerAuthenticationOptions
|
||||||
|
{
|
||||||
|
ServerCertificate = localhostCert
|
||||||
|
});
|
||||||
|
}, state: null);
|
||||||
});
|
});
|
||||||
|
|
||||||
options
|
options
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandshakeDetailsAreAvailableAfterAsyncCallback()
|
||||||
|
{
|
||||||
|
void ConfigureListenOptions(ListenOptions listenOptions)
|
||||||
|
{
|
||||||
|
listenOptions.UseHttps(async (stream, clientHelloInfo, state, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
return new SslServerAuthenticationOptions
|
||||||
|
{
|
||||||
|
ServerCertificate = _x509Certificate2,
|
||||||
|
};
|
||||||
|
}, state: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
await using (var server = new TestServer(context =>
|
||||||
|
{
|
||||||
|
var tlsFeature = context.Features.Get<ITlsHandshakeFeature>();
|
||||||
|
Assert.NotNull(tlsFeature);
|
||||||
|
Assert.True(tlsFeature.Protocol > SslProtocols.None, "Protocol");
|
||||||
|
Assert.True(tlsFeature.CipherAlgorithm > CipherAlgorithmType.Null, "Cipher");
|
||||||
|
Assert.True(tlsFeature.CipherStrength > 0, "CipherStrength");
|
||||||
|
Assert.True(tlsFeature.HashAlgorithm >= HashAlgorithmType.None, "HashAlgorithm"); // May be None on Linux.
|
||||||
|
Assert.True(tlsFeature.HashStrength >= 0, "HashStrength"); // May be 0 for some algorithms
|
||||||
|
Assert.True(tlsFeature.KeyExchangeAlgorithm >= ExchangeAlgorithmType.None, "KeyExchangeAlgorithm"); // Maybe None on Windows 7
|
||||||
|
Assert.True(tlsFeature.KeyExchangeStrength >= 0, "KeyExchangeStrength"); // May be 0 on mac
|
||||||
|
|
||||||
|
return context.Response.WriteAsync("hello world");
|
||||||
|
}, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
|
||||||
|
{
|
||||||
|
var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
|
||||||
|
Assert.Equal("hello world", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RequireCertificateFailsWhenNoCertificate()
|
public async Task RequireCertificateFailsWhenNoCertificate()
|
||||||
{
|
{
|
||||||
|
|
@ -166,22 +202,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[QuarantinedTest("https://github.com/dotnet/runtime/issues/40402")]
|
public async Task AsyncCallbackSettingClientCertificateRequiredContinuesWhenNoCertificate()
|
||||||
public async Task ClientCertificateRequiredConfiguredInCallbackContinuesWhenNoCertificate()
|
|
||||||
{
|
{
|
||||||
void ConfigureListenOptions(ListenOptions listenOptions)
|
void ConfigureListenOptions(ListenOptions listenOptions)
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps((connection, stream, clientHelloInfo, state, cancellationToken) =>
|
listenOptions.UseHttps((stream, clientHelloInfo, state, cancellationToken) =>
|
||||||
new ValueTask<SslServerAuthenticationOptions>(new SslServerAuthenticationOptions
|
new ValueTask<SslServerAuthenticationOptions>(new SslServerAuthenticationOptions
|
||||||
{
|
{
|
||||||
ServerCertificate = _x509Certificate2,
|
ServerCertificate = _x509Certificate2,
|
||||||
// From the API Docs: "Note that this is only a request --
|
|
||||||
// if no certificate is provided, the server still accepts the connection request."
|
|
||||||
// Not to mention this is equivalent to the test above.
|
|
||||||
ClientCertificateRequired = true,
|
ClientCertificateRequired = true,
|
||||||
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
|
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
|
||||||
CertificateRevocationCheckMode = X509RevocationMode.NoCheck
|
CertificateRevocationCheckMode = X509RevocationMode.NoCheck
|
||||||
}), state: null, HttpsConnectionAdapterOptions.DefaultHandshakeTimeout);
|
}), state: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
await using (var server = new TestServer(context =>
|
await using (var server = new TestServer(context =>
|
||||||
|
|
@ -255,6 +287,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UsesProvidedAsyncCallback()
|
||||||
|
{
|
||||||
|
var selectorCalled = 0;
|
||||||
|
void ConfigureListenOptions(ListenOptions listenOptions)
|
||||||
|
{
|
||||||
|
listenOptions.UseHttps(async (stream, clientHelloInfo, state, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
Assert.NotNull(stream);
|
||||||
|
Assert.Equal("localhost", clientHelloInfo.ServerName);
|
||||||
|
selectorCalled++;
|
||||||
|
|
||||||
|
return new SslServerAuthenticationOptions
|
||||||
|
{
|
||||||
|
ServerCertificate = _x509Certificate2
|
||||||
|
};
|
||||||
|
}, state: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
var stream = OpenSslStream(connection.Stream);
|
||||||
|
await stream.AuthenticateAsClientAsync("localhost");
|
||||||
|
Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
|
||||||
|
Assert.Equal(1, selectorCalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task UsesProvidedServerCertificateSelectorEachTime()
|
public async Task UsesProvidedServerCertificateSelectorEachTime()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
@ -13,7 +12,6 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
|
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
|
||||||
|
|
@ -21,13 +19,14 @@ using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
public class HttpsTests : LoggedTest
|
public class HttpsTests : LoggedTest
|
||||||
{
|
{
|
||||||
|
private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate();
|
||||||
|
|
||||||
private KestrelServerOptions CreateServerOptions()
|
private KestrelServerOptions CreateServerOptions()
|
||||||
{
|
{
|
||||||
var serverOptions = new KestrelServerOptions();
|
var serverOptions = new KestrelServerOptions();
|
||||||
|
|
@ -41,8 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
public void UseHttpsDefaultsToDefaultCert()
|
public void UseHttpsDefaultsToDefaultCert()
|
||||||
{
|
{
|
||||||
var serverOptions = CreateServerOptions();
|
var serverOptions = CreateServerOptions();
|
||||||
var defaultCert = TestResources.GetTestCertificate();
|
serverOptions.DefaultCertificate = _x509Certificate2;
|
||||||
serverOptions.DefaultCertificate = defaultCert;
|
|
||||||
|
|
||||||
serverOptions.ListenLocalhost(5000, options =>
|
serverOptions.ListenLocalhost(5000, options =>
|
||||||
{
|
{
|
||||||
|
|
@ -62,22 +60,51 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
Assert.False(serverOptions.IsDevCertLoaded);
|
Assert.False(serverOptions.IsDevCertLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UseHttpsWithAsyncCallbackDoeNotFallBackToDefaultCert()
|
||||||
|
{
|
||||||
|
var loggerProvider = new HandshakeErrorLoggerProvider();
|
||||||
|
LoggerFactory.AddProvider(loggerProvider);
|
||||||
|
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
await using (var server = new TestServer(context => Task.CompletedTask,
|
||||||
|
testContext,
|
||||||
|
listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.UseHttps((stream, clientHelloInfo, state, cancellationToken) =>
|
||||||
|
new ValueTask<SslServerAuthenticationOptions>(new SslServerAuthenticationOptions()), state: null);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true))
|
||||||
|
{
|
||||||
|
var ex = await Assert.ThrowsAnyAsync<Exception>(() =>
|
||||||
|
sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null,
|
||||||
|
enabledSslProtocols: SslProtocols.Tls,
|
||||||
|
checkCertificateRevocation: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorException = Assert.Single(loggerProvider.ErrorLogger.ErrorExceptions);
|
||||||
|
Assert.IsType<NotSupportedException>(errorException);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ConfigureHttpsDefaultsNeverLoadsDefaultCert()
|
public void ConfigureHttpsDefaultsNeverLoadsDefaultCert()
|
||||||
{
|
{
|
||||||
var serverOptions = CreateServerOptions();
|
var serverOptions = CreateServerOptions();
|
||||||
var testCert = TestResources.GetTestCertificate();
|
|
||||||
serverOptions.ConfigureHttpsDefaults(options =>
|
serverOptions.ConfigureHttpsDefaults(options =>
|
||||||
{
|
{
|
||||||
Assert.Null(options.ServerCertificate);
|
Assert.Null(options.ServerCertificate);
|
||||||
options.ServerCertificate = testCert;
|
options.ServerCertificate = _x509Certificate2;
|
||||||
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
|
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
|
||||||
});
|
});
|
||||||
serverOptions.ListenLocalhost(5000, options =>
|
serverOptions.ListenLocalhost(5000, options =>
|
||||||
{
|
{
|
||||||
options.UseHttps(opt =>
|
options.UseHttps(opt =>
|
||||||
{
|
{
|
||||||
Assert.Equal(testCert, opt.ServerCertificate);
|
Assert.Equal(_x509Certificate2, opt.ServerCertificate);
|
||||||
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode);
|
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -90,14 +117,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
public void ConfigureCertSelectorNeverLoadsDefaultCert()
|
public void ConfigureCertSelectorNeverLoadsDefaultCert()
|
||||||
{
|
{
|
||||||
var serverOptions = CreateServerOptions();
|
var serverOptions = CreateServerOptions();
|
||||||
var testCert = TestResources.GetTestCertificate();
|
|
||||||
serverOptions.ConfigureHttpsDefaults(options =>
|
serverOptions.ConfigureHttpsDefaults(options =>
|
||||||
{
|
{
|
||||||
Assert.Null(options.ServerCertificate);
|
Assert.Null(options.ServerCertificate);
|
||||||
Assert.Null(options.ServerCertificateSelector);
|
Assert.Null(options.ServerCertificateSelector);
|
||||||
options.ServerCertificateSelector = (features, name) =>
|
options.ServerCertificateSelector = (features, name) =>
|
||||||
{
|
{
|
||||||
return testCert;
|
return _x509Certificate2;
|
||||||
};
|
};
|
||||||
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
|
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
|
||||||
});
|
});
|
||||||
|
|
@ -126,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
new TestServiceContext(LoggerFactory),
|
new TestServiceContext(LoggerFactory),
|
||||||
listenOptions =>
|
listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(TestResources.GetTestCertificate());
|
listenOptions.UseHttps(_x509Certificate2);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -139,7 +165,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
|
|
||||||
Assert.Equal(1, loggerProvider.FilterLogger.LastEventId.Id);
|
Assert.Equal(1, loggerProvider.FilterLogger.LastEventId.Id);
|
||||||
Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel);
|
Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel);
|
||||||
Assert.True(loggerProvider.ErrorLogger.TotalErrorsLogged == 0,
|
Assert.True(loggerProvider.ErrorLogger.ErrorMessages.Count == 0,
|
||||||
userMessage: string.Join(Environment.NewLine, loggerProvider.ErrorLogger.ErrorMessages));
|
userMessage: string.Join(Environment.NewLine, loggerProvider.ErrorLogger.ErrorMessages));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +180,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
new TestServiceContext(LoggerFactory),
|
new TestServiceContext(LoggerFactory),
|
||||||
listenOptions =>
|
listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(TestResources.GetTestCertificate());
|
listenOptions.UseHttps(_x509Certificate2);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -168,7 +194,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
|
|
||||||
Assert.Equal(1, loggerProvider.FilterLogger.LastEventId.Id);
|
Assert.Equal(1, loggerProvider.FilterLogger.LastEventId.Id);
|
||||||
Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel);
|
Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel);
|
||||||
Assert.True(loggerProvider.ErrorLogger.TotalErrorsLogged == 0,
|
Assert.True(loggerProvider.ErrorLogger.ErrorMessages.Count == 0,
|
||||||
userMessage: string.Join(Environment.NewLine, loggerProvider.ErrorLogger.ErrorMessages));
|
userMessage: string.Join(Environment.NewLine, loggerProvider.ErrorLogger.ErrorMessages));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,7 +224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
new TestServiceContext(LoggerFactory),
|
new TestServiceContext(LoggerFactory),
|
||||||
listenOptions =>
|
listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(TestResources.GetTestCertificate());
|
listenOptions.UseHttps(_x509Certificate2);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -242,7 +268,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
new TestServiceContext(LoggerFactory),
|
new TestServiceContext(LoggerFactory),
|
||||||
listenOptions =>
|
listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(TestResources.GetTestCertificate());
|
listenOptions.UseHttps(_x509Certificate2);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -273,7 +299,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
new TestServiceContext(LoggerFactory),
|
new TestServiceContext(LoggerFactory),
|
||||||
listenOptions =>
|
listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(TestResources.GetTestCertificate());
|
listenOptions.UseHttps(_x509Certificate2);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -299,7 +325,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
new TestServiceContext(LoggerFactory),
|
new TestServiceContext(LoggerFactory),
|
||||||
listenOptions =>
|
listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(TestResources.GetTestCertificate());
|
listenOptions.UseHttps(_x509Certificate2);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -316,10 +342,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
LoggerFactory.AddProvider(loggerProvider);
|
LoggerFactory.AddProvider(loggerProvider);
|
||||||
|
|
||||||
var testContext = new TestServiceContext(LoggerFactory);
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager);
|
|
||||||
|
|
||||||
var handshakeStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
TimeSpan handshakeTimeout = default;
|
|
||||||
|
|
||||||
await using (var server = new TestServer(context => Task.CompletedTask,
|
await using (var server = new TestServer(context => Task.CompletedTask,
|
||||||
testContext,
|
testContext,
|
||||||
|
|
@ -327,26 +349,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(o =>
|
listenOptions.UseHttps(o =>
|
||||||
{
|
{
|
||||||
o.ServerCertificate = new X509Certificate2(TestResources.GetTestCertificate());
|
o.ServerCertificate = new X509Certificate2(_x509Certificate2);
|
||||||
o.OnAuthenticate = (_, __) =>
|
o.HandshakeTimeout = TimeSpan.FromMilliseconds(100);
|
||||||
{
|
|
||||||
handshakeStartedTcs.SetResult();
|
|
||||||
};
|
|
||||||
|
|
||||||
handshakeTimeout = o.HandshakeTimeout;
|
|
||||||
});
|
});
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
// HttpsConnectionAdapter dispatches via Task.Run() before starting the handshake.
|
Assert.Equal(0, await connection.Stream.ReadAsync(new byte[1], 0, 1).DefaultTimeout());
|
||||||
// Wait for the handshake to start before advancing the system clock.
|
}
|
||||||
await handshakeStartedTcs.Task.DefaultTimeout();
|
}
|
||||||
|
|
||||||
// Min amount of time between requests that triggers a handshake timeout.
|
await loggerProvider.FilterLogger.LogTcs.Task.DefaultTimeout();
|
||||||
testContext.MockSystemClock.UtcNow += handshakeTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1);
|
Assert.Equal(2, loggerProvider.FilterLogger.LastEventId);
|
||||||
heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow);
|
Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandshakeTimesOutAndIsLoggedAsDebugWithAsyncCallback()
|
||||||
|
{
|
||||||
|
var loggerProvider = new HandshakeErrorLoggerProvider();
|
||||||
|
LoggerFactory.AddProvider(loggerProvider);
|
||||||
|
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
|
||||||
|
await using (var server = new TestServer(context => Task.CompletedTask,
|
||||||
|
testContext,
|
||||||
|
listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.UseHttps(async (stream, clientHelloInfo, state, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
return new SslServerAuthenticationOptions
|
||||||
|
{
|
||||||
|
ServerCertificate = _x509Certificate2,
|
||||||
|
};
|
||||||
|
}, state: null, handshakeTimeout: TimeSpan.FromMilliseconds(100));
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
Assert.Equal(0, await connection.Stream.ReadAsync(new byte[1], 0, 1).DefaultTimeout());
|
Assert.Equal(0, await connection.Stream.ReadAsync(new byte[1], 0, 1).DefaultTimeout());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +437,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
var loggerProvider = new HandshakeErrorLoggerProvider();
|
var loggerProvider = new HandshakeErrorLoggerProvider();
|
||||||
LoggerFactory.AddProvider(loggerProvider);
|
LoggerFactory.AddProvider(loggerProvider);
|
||||||
|
|
||||||
var testCert = TestResources.GetTestCertificate();
|
var testCert = _x509Certificate2;
|
||||||
var onAuthenticateCalled = false;
|
var onAuthenticateCalled = false;
|
||||||
|
|
||||||
await using (var server = new TestServer(context => Task.CompletedTask,
|
await using (var server = new TestServer(context => Task.CompletedTask,
|
||||||
|
|
@ -430,7 +473,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
var loggerProvider = new HandshakeErrorLoggerProvider();
|
var loggerProvider = new HandshakeErrorLoggerProvider();
|
||||||
LoggerFactory.AddProvider(loggerProvider);
|
LoggerFactory.AddProvider(loggerProvider);
|
||||||
|
|
||||||
var testCert = TestResources.GetTestCertificate();
|
var testCert = _x509Certificate2;
|
||||||
var onAuthenticateCalled = false;
|
var onAuthenticateCalled = false;
|
||||||
|
|
||||||
await using (var server = new TestServer(context => Task.CompletedTask,
|
await using (var server = new TestServer(context => Task.CompletedTask,
|
||||||
|
|
@ -511,11 +554,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
|
|
||||||
private class ApplicationErrorLogger : ILogger
|
private class ApplicationErrorLogger : ILogger
|
||||||
{
|
{
|
||||||
private List<string> _errorMessages = new List<string>();
|
public List<string> ErrorMessages => new List<string>();
|
||||||
|
public List<Exception> ErrorExceptions { get; } = new List<Exception>();
|
||||||
public IEnumerable<string> ErrorMessages => _errorMessages;
|
|
||||||
|
|
||||||
public int TotalErrorsLogged => _errorMessages.Count;
|
|
||||||
|
|
||||||
public bool ObjectDisposedExceptionLogged { get; set; }
|
public bool ObjectDisposedExceptionLogged { get; set; }
|
||||||
|
|
||||||
|
|
@ -524,7 +564,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
if (logLevel == LogLevel.Error)
|
if (logLevel == LogLevel.Error)
|
||||||
{
|
{
|
||||||
var log = $"Log {logLevel}[{eventId}]: {formatter(state, exception)} {exception}";
|
var log = $"Log {logLevel}[{eventId}]: {formatter(state, exception)} {exception}";
|
||||||
_errorMessages.Add(log);
|
ErrorMessages.Add(log);
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
ErrorExceptions.Add(exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception is ObjectDisposedException)
|
if (exception is ObjectDisposedException)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue