Downgrade or throw when HTTP/2 over TLS is configured on older Windows versions (#22859)
HTTP/2 over TLS is not compatible with Windows versions strictly older than Windows 10 or Windows Server 2016. Update kestrel to: - Downgrade to HTTP/1.1 when Http1AndHttp2 is configured. - Throw NotSupportedException when Http2 is configured. - Allow HTTP/2 over TLS to be enabled if AppContext switch Microsoft.AspNetCore.Server.Kestrel.EnableWindows81Http2 is set. This allows users who have configured cipher suites on Windows 8.1 and Windows Server 2012 R2 to continue using HTTP/2 over TLS.
This commit is contained in:
parent
f48f558f48
commit
45e6571649
|
|
@ -563,12 +563,9 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
|||
<data name="RequestTrailersNotAvailable" xml:space="preserve">
|
||||
<value>The request trailers are not available yet. They may not be available until the full request body is read.</value>
|
||||
</data>
|
||||
<data name="HTTP2NoTlsOsx" xml:space="preserve">
|
||||
<data name="Http2NoTlsOsx" xml:space="preserve">
|
||||
<value>HTTP/2 over TLS is not supported on macOS due to missing ALPN support.</value>
|
||||
</data>
|
||||
<data name="HTTP2NoTlsWin7" xml:space="preserve">
|
||||
<value>HTTP/2 over TLS is not supported on Windows 7 due to missing ALPN support.</value>
|
||||
</data>
|
||||
<data name="Http2StreamResetByApplication" xml:space="preserve">
|
||||
<value>The HTTP/2 stream was reset by the application with error code {errorCode}.</value>
|
||||
</data>
|
||||
|
|
@ -605,6 +602,12 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
|||
<data name="HttpsConnectionEstablished" xml:space="preserve">
|
||||
<value>Connection "{connectionId}" established using the following protocol: {protocol}</value>
|
||||
</data>
|
||||
<data name="Http2DefaultCiphersInsufficient" xml:space="preserve">
|
||||
<value>HTTP/2 over TLS is not supported on Windows versions older than Windows 10 and Windows Server 2016 due to incompatible ciphers or missing ALPN support. Falling back to HTTP/1.1 instead.</value>
|
||||
</data>
|
||||
<data name="Http2NoTlsWin81" xml:space="preserve">
|
||||
<value>HTTP/2 over TLS is not supported on Windows versions earlier than Windows 10 and Windows Server 2016 due to incompatible ciphers or missing ALPN support.</value>
|
||||
</data>
|
||||
<data name="Http2ErrorKeepAliveTimeout" xml:space="preserve">
|
||||
<value>Timeout while waiting for incoming HTTP/2 frames after a keep alive ping.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
{
|
||||
internal class HttpsConnectionMiddleware
|
||||
{
|
||||
private const string EnableWindows81Http2 = "Microsoft.AspNetCore.Server.Kestrel.EnableWindows81Http2";
|
||||
private readonly ConnectionDelegate _next;
|
||||
private readonly HttpsConnectionAdapterOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
|
@ -43,18 +44,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_options = options;
|
||||
_logger = loggerFactory.CreateLogger<HttpsConnectionMiddleware>();
|
||||
|
||||
// This configuration will always fail per-request, preemptively fail it here. See HttpConnection.SelectProtocol().
|
||||
if (options.HttpProtocols == HttpProtocols.Http2)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
throw new NotSupportedException(CoreStrings.HTTP2NoTlsOsx);
|
||||
throw new NotSupportedException(CoreStrings.Http2NoTlsOsx);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version < new Version(6, 2))
|
||||
else if (IsWindowsVersionIncompatible())
|
||||
{
|
||||
throw new NotSupportedException(CoreStrings.HTTP2NoTlsWin7);
|
||||
throw new NotSupportedException(CoreStrings.Http2NoTlsWin81);
|
||||
}
|
||||
}
|
||||
else if (options.HttpProtocols == HttpProtocols.Http1AndHttp2 && IsWindowsVersionIncompatible())
|
||||
{
|
||||
_logger.Http2DefaultCiphersInsufficient();
|
||||
options.HttpProtocols = HttpProtocols.Http1;
|
||||
}
|
||||
|
||||
_next = next;
|
||||
// capture the certificate now so it can't be switched after validation
|
||||
|
|
@ -75,9 +84,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
{
|
||||
EnsureCertificateIsAllowedForServerAuth(_serverCertificate);
|
||||
}
|
||||
|
||||
_options = options;
|
||||
_logger = loggerFactory.CreateLogger<HttpsConnectionMiddleware>();
|
||||
}
|
||||
|
||||
public async Task OnConnectionAsync(ConnectionContext context)
|
||||
|
|
@ -214,7 +220,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
||||
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
||||
|
||||
_logger.LogDebug(2, CoreStrings.AuthenticationTimedOut);
|
||||
_logger.AuthenticationTimedOut();
|
||||
await sslStream.DisposeAsync();
|
||||
return;
|
||||
}
|
||||
|
|
@ -223,7 +229,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
||||
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
||||
|
||||
_logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
|
||||
_logger.AuthenticationFailed(ex);
|
||||
await sslStream.DisposeAsync();
|
||||
return;
|
||||
}
|
||||
|
|
@ -232,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
||||
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
||||
|
||||
_logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
|
||||
_logger.AuthenticationFailed(ex);
|
||||
|
||||
await sslStream.DisposeAsync();
|
||||
return;
|
||||
|
|
@ -252,7 +258,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
|
||||
KestrelEventSource.Log.TlsHandshakeStop(context, feature);
|
||||
|
||||
_logger.LogDebug(3, CoreStrings.HttpsConnectionEstablished, context.ConnectionId, sslStream.SslProtocol);
|
||||
_logger.HttpsConnectionEstablished(context.ConnectionId, sslStream.SslProtocol);
|
||||
|
||||
var originalTransport = context.Transport;
|
||||
|
||||
|
|
@ -298,5 +304,57 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
|||
|
||||
return new X509Certificate2(certificate);
|
||||
}
|
||||
|
||||
private static bool IsWindowsVersionIncompatible()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var enableHttp2OnWindows81 = AppContext.TryGetSwitch(EnableWindows81Http2, out var enabled) && enabled;
|
||||
if (Environment.OSVersion.Version < new Version(6, 3) // Missing ALPN support
|
||||
// Win8.1 and 2012 R2 don't support the right cipher configuration by default.
|
||||
|| (Environment.OSVersion.Version < new Version(10, 0) && !enableHttp2OnWindows81))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class HttpsConnectionMiddlewareLoggerExtensions
|
||||
{
|
||||
|
||||
private static readonly Action<ILogger, Exception> _authenticationFailed =
|
||||
LoggerMessage.Define(
|
||||
logLevel: LogLevel.Debug,
|
||||
eventId: new EventId(1, "AuthenticationFailed"),
|
||||
formatString: CoreStrings.AuthenticationFailed);
|
||||
|
||||
private static readonly Action<ILogger, Exception> _authenticationTimedOut =
|
||||
LoggerMessage.Define(
|
||||
logLevel: LogLevel.Debug,
|
||||
eventId: new EventId(2, "AuthenticationTimedOut"),
|
||||
formatString: CoreStrings.AuthenticationTimedOut);
|
||||
|
||||
private static readonly Action<ILogger, string, SslProtocols, Exception> _httpsConnectionEstablished =
|
||||
LoggerMessage.Define<string, SslProtocols>(
|
||||
logLevel: LogLevel.Debug,
|
||||
eventId: new EventId(3, "HttpsConnectionEstablished"),
|
||||
formatString: CoreStrings.HttpsConnectionEstablished);
|
||||
|
||||
private static readonly Action<ILogger, Exception> _http2DefaultCiphersInsufficient =
|
||||
LoggerMessage.Define(
|
||||
logLevel: LogLevel.Information,
|
||||
eventId: new EventId(4, "Http2DefaultCiphersInsufficient"),
|
||||
formatString: CoreStrings.Http2DefaultCiphersInsufficient);
|
||||
|
||||
public static void AuthenticationFailed(this ILogger logger, Exception exception) => _authenticationFailed(logger, exception);
|
||||
|
||||
public static void AuthenticationTimedOut(this ILogger logger) => _authenticationTimedOut(logger, null);
|
||||
|
||||
public static void HttpsConnectionEstablished(this ILogger logger, string connectionId, SslProtocols sslProtocol) => _httpsConnectionEstablished(logger, connectionId, sslProtocol, null);
|
||||
|
||||
public static void Http2DefaultCiphersInsufficient(this ILogger logger) => _http2DefaultCiphersInsufficient(logger, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
|||
[ConditionalFact]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
|
||||
[SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/10428", Queues = "Debian.8.Amd64;Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2
|
||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
|
||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
|
||||
public async Task TlsAlpnHandshakeSelectsHttp2From1and2()
|
||||
{
|
||||
using (var server = new TestServer(context =>
|
||||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
|||
[ConditionalFact]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
|
||||
[SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/10428", Queues = "Debian.8.Amd64;Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2
|
||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
|
||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
|
||||
public async Task TlsAlpnHandshakeSelectsHttp2()
|
||||
{
|
||||
using (var server = new TestServer(context =>
|
||||
|
|
|
|||
|
|
@ -594,7 +594,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
[InlineData(HttpProtocols.Http1AndHttp2)]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
|
||||
[SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/10428", Queues = "Debian.8.Amd64;Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2
|
||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
|
||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
|
||||
public async Task ListenOptionsProtolsCanBeSetAfterUseHttps(HttpProtocols httpProtocols)
|
||||
{
|
||||
void ConfigureListenOptions(ListenOptions listenOptions)
|
||||
|
|
@ -623,6 +623,65 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
stream.NegotiatedApplicationProtocol);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Downgrade logic only applies on Windows")]
|
||||
[MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
|
||||
public void Http1AndHttp2DowngradeToHttp1ForHttpsOnIncompatibleWindowsVersions()
|
||||
{
|
||||
var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
HttpProtocols = HttpProtocols.Http1AndHttp2
|
||||
};
|
||||
new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions);
|
||||
|
||||
Assert.Equal(HttpProtocols.Http1, httpConnectionAdapterOptions.HttpProtocols);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Downgrade logic only applies on Windows")]
|
||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
|
||||
public void Http1AndHttp2DoesNotDowngradeOnCompatibleWindowsVersions()
|
||||
{
|
||||
var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
HttpProtocols = HttpProtocols.Http1AndHttp2
|
||||
};
|
||||
new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions);
|
||||
|
||||
Assert.Equal(HttpProtocols.Http1AndHttp2, httpConnectionAdapterOptions.HttpProtocols);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Error logic only applies on Windows")]
|
||||
[MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
|
||||
public void Http2ThrowsOnIncompatibleWindowsVersions()
|
||||
{
|
||||
var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
HttpProtocols = HttpProtocols.Http2
|
||||
};
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions));
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Error logic only applies on Windows")]
|
||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
|
||||
public void Http2DoesNotThrowOnCompatibleWindowsVersions()
|
||||
{
|
||||
var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
|
||||
{
|
||||
ServerCertificate = _x509Certificate2,
|
||||
HttpProtocols = HttpProtocols.Http2
|
||||
};
|
||||
|
||||
// Does not throw
|
||||
new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions);
|
||||
}
|
||||
|
||||
private static async Task App(HttpContext httpContext)
|
||||
{
|
||||
var request = httpContext.Request;
|
||||
|
|
|
|||
Loading…
Reference in New Issue