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">
|
<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>
|
<value>The request trailers are not available yet. They may not be available until the full request body is read.</value>
|
||||||
</data>
|
</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>
|
<value>HTTP/2 over TLS is not supported on macOS due to missing ALPN support.</value>
|
||||||
</data>
|
</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">
|
<data name="Http2StreamResetByApplication" xml:space="preserve">
|
||||||
<value>The HTTP/2 stream was reset by the application with error code {errorCode}.</value>
|
<value>The HTTP/2 stream was reset by the application with error code {errorCode}.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -605,6 +602,12 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
||||||
<data name="HttpsConnectionEstablished" xml:space="preserve">
|
<data name="HttpsConnectionEstablished" xml:space="preserve">
|
||||||
<value>Connection "{connectionId}" established using the following protocol: {protocol}</value>
|
<value>Connection "{connectionId}" established using the following protocol: {protocol}</value>
|
||||||
</data>
|
</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">
|
<data name="Http2ErrorKeepAliveTimeout" xml:space="preserve">
|
||||||
<value>Timeout while waiting for incoming HTTP/2 frames after a keep alive ping.</value>
|
<value>Timeout while waiting for incoming HTTP/2 frames after a keep alive ping.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
{
|
{
|
||||||
internal class HttpsConnectionMiddleware
|
internal class HttpsConnectionMiddleware
|
||||||
{
|
{
|
||||||
|
private const string EnableWindows81Http2 = "Microsoft.AspNetCore.Server.Kestrel.EnableWindows81Http2";
|
||||||
private readonly ConnectionDelegate _next;
|
private readonly ConnectionDelegate _next;
|
||||||
private readonly HttpsConnectionAdapterOptions _options;
|
private readonly HttpsConnectionAdapterOptions _options;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
@ -43,18 +44,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
throw new ArgumentNullException(nameof(options));
|
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().
|
// This configuration will always fail per-request, preemptively fail it here. See HttpConnection.SelectProtocol().
|
||||||
if (options.HttpProtocols == HttpProtocols.Http2)
|
if (options.HttpProtocols == HttpProtocols.Http2)
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
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;
|
_next = next;
|
||||||
// capture the certificate now so it can't be switched after validation
|
// 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);
|
EnsureCertificateIsAllowedForServerAuth(_serverCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
_options = options;
|
|
||||||
_logger = loggerFactory.CreateLogger<HttpsConnectionMiddleware>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnConnectionAsync(ConnectionContext context)
|
public async Task OnConnectionAsync(ConnectionContext context)
|
||||||
|
|
@ -214,7 +220,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
||||||
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
||||||
|
|
||||||
_logger.LogDebug(2, CoreStrings.AuthenticationTimedOut);
|
_logger.AuthenticationTimedOut();
|
||||||
await sslStream.DisposeAsync();
|
await sslStream.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -223,7 +229,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
||||||
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
||||||
|
|
||||||
_logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
|
_logger.AuthenticationFailed(ex);
|
||||||
await sslStream.DisposeAsync();
|
await sslStream.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId);
|
||||||
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
KestrelEventSource.Log.TlsHandshakeStop(context, null);
|
||||||
|
|
||||||
_logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
|
_logger.AuthenticationFailed(ex);
|
||||||
|
|
||||||
await sslStream.DisposeAsync();
|
await sslStream.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
|
|
@ -252,7 +258,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
|
|
||||||
KestrelEventSource.Log.TlsHandshakeStop(context, feature);
|
KestrelEventSource.Log.TlsHandshakeStop(context, feature);
|
||||||
|
|
||||||
_logger.LogDebug(3, CoreStrings.HttpsConnectionEstablished, context.ConnectionId, sslStream.SslProtocol);
|
_logger.HttpsConnectionEstablished(context.ConnectionId, sslStream.SslProtocol);
|
||||||
|
|
||||||
var originalTransport = context.Transport;
|
var originalTransport = context.Transport;
|
||||||
|
|
||||||
|
|
@ -298,5 +304,57 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
|
|
||||||
return new X509Certificate2(certificate);
|
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]
|
[ConditionalFact]
|
||||||
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
|
[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
|
[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()
|
public async Task TlsAlpnHandshakeSelectsHttp2From1and2()
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context =>
|
using (var server = new TestServer(context =>
|
||||||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
||||||
[ConditionalFact]
|
[ConditionalFact]
|
||||||
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
|
[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
|
[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()
|
public async Task TlsAlpnHandshakeSelectsHttp2()
|
||||||
{
|
{
|
||||||
using (var server = new TestServer(context =>
|
using (var server = new TestServer(context =>
|
||||||
|
|
|
||||||
|
|
@ -594,7 +594,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
[InlineData(HttpProtocols.Http1AndHttp2)]
|
[InlineData(HttpProtocols.Http1AndHttp2)]
|
||||||
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
|
[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
|
[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)
|
public async Task ListenOptionsProtolsCanBeSetAfterUseHttps(HttpProtocols httpProtocols)
|
||||||
{
|
{
|
||||||
void ConfigureListenOptions(ListenOptions listenOptions)
|
void ConfigureListenOptions(ListenOptions listenOptions)
|
||||||
|
|
@ -623,6 +623,65 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
stream.NegotiatedApplicationProtocol);
|
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)
|
private static async Task App(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue