Expose Tls details as a feature. #2661

Limit Http/2 to TLS 1.2 #2251
Bootstrap functional tests #2238
This commit is contained in:
Chris Ross (ASP.NET) 2018-06-13 11:53:19 -07:00
parent c18a27d1f1
commit c498f03cb4
20 changed files with 504 additions and 85 deletions

View File

@ -5,47 +5,48 @@
<!-- This files is typically managed by automation. Execute 'run.ps1 upgrade deps' to update these variables to the last-known-good versions. -->
<PropertyGroup Label="Package Versions">
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<InternalAspNetCoreAnalyzersPackageVersion>2.2.0-preview1-34411</InternalAspNetCoreAnalyzersPackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview1-17081</InternalAspNetCoreSdkPackageVersion>
<InternalAspNetCoreAnalyzersPackageVersion>2.2.0-preview1-34484</InternalAspNetCoreAnalyzersPackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview1-17087</InternalAspNetCoreSdkPackageVersion>
<LibuvPackageVersion>1.10.0</LibuvPackageVersion>
<MicrosoftAspNetCoreAllPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreAllPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpFeaturesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHttpFeaturesPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-preview1-34411</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>
<MicrosoftExtensionsBuffersMemoryPoolSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsBuffersMemoryPoolSourcesPackageVersion>
<MicrosoftExtensionsBuffersSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsBuffersSourcesPackageVersion>
<MicrosoftExtensionsBuffersTestingSourcesPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsBuffersTestingSourcesPackageVersion>
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsConfigurationBinderPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview1-34411</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftAspNetCoreAllPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreAllPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpFeaturesPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreHttpFeaturesPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-preview1-34484</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion>
<MicrosoftExtensionsBuffersMemoryPoolSourcesPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsBuffersMemoryPoolSourcesPackageVersion>
<MicrosoftExtensionsBuffersSourcesPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsBuffersSourcesPackageVersion>
<MicrosoftExtensionsBuffersTestingSourcesPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsBuffersTestingSourcesPackageVersion>
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsConfigurationBinderPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview1-34484</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26606-01</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>2.2.0-preview1-34411</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26614-02</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>2.2.0-preview1-34484</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
<SystemBuffersPackageVersion>4.6.0-preview1-26605-01</SystemBuffersPackageVersion>
<SystemIOPipelinesPackageVersion>4.6.0-preview1-26605-01</SystemIOPipelinesPackageVersion>
<SystemMemoryPackageVersion>4.6.0-preview1-26605-01</SystemMemoryPackageVersion>
<SystemNumericsVectorsPackageVersion>4.6.0-preview1-26605-01</SystemNumericsVectorsPackageVersion>
<SystemRuntimeCompilerServicesUnsafePackageVersion>4.6.0-preview1-26605-01</SystemRuntimeCompilerServicesUnsafePackageVersion>
<SystemSecurityCryptographyCngPackageVersion>4.6.0-preview1-26605-01</SystemSecurityCryptographyCngPackageVersion>
<SystemThreadingTasksExtensionsPackageVersion>4.6.0-preview1-26605-01</SystemThreadingTasksExtensionsPackageVersion>
<SystemBuffersPackageVersion>4.6.0-preview1-26613-07</SystemBuffersPackageVersion>
<SystemIOPipelinesPackageVersion>4.6.0-preview1-26613-07</SystemIOPipelinesPackageVersion>
<SystemMemoryPackageVersion>4.6.0-preview1-26613-07</SystemMemoryPackageVersion>
<SystemNetHttpWinHttpHandlerPackageVersion>4.6.0-preview1-26613-07</SystemNetHttpWinHttpHandlerPackageVersion>
<SystemNumericsVectorsPackageVersion>4.6.0-preview1-26613-07</SystemNumericsVectorsPackageVersion>
<SystemRuntimeCompilerServicesUnsafePackageVersion>4.6.0-preview1-26613-07</SystemRuntimeCompilerServicesUnsafePackageVersion>
<SystemSecurityCryptographyCngPackageVersion>4.6.0-preview1-26613-07</SystemSecurityCryptographyCngPackageVersion>
<SystemThreadingTasksExtensionsPackageVersion>4.6.0-preview1-26613-07</SystemThreadingTasksExtensionsPackageVersion>
<Utf8JsonPackageVersion>1.3.7</Utf8JsonPackageVersion>
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>
<XunitPackageVersion>2.3.1</XunitPackageVersion>

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Authentication;
namespace Microsoft.AspNetCore.Connections.Features
{
public interface ITlsHandshakeFeature
{
SslProtocols Protocol { get; }
CipherAlgorithmType CipherAlgorithm { get; }
int CipherStrength { get; }
HashAlgorithmType HashAlgorithm { get; }
int HashStrength { get; }
ExchangeAlgorithmType KeyExchangeAlgorithm { get; }
int KeyExchangeStrength { get; }
}
}

View File

@ -518,4 +518,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
<data name="Http2ErrorFrameOverLimit" xml:space="preserve">
<value>The received frame size of {size} exceeds the limit {limit}.</value>
</data>
<data name="Http2ErrorMinTlsVersion" xml:space="preserve">
<value>Tls 1.2 or later must be used for HTTP/2. {protocol} was negotiated.</value>
</data>
</root>

View File

@ -3,14 +3,15 @@
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Concurrent;
using System.IO.Pipelines;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -115,31 +116,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
try
{
while (!_stopping)
{
var result = await Input.ReadAsync();
var readableBuffer = result.Buffer;
var consumed = readableBuffer.Start;
var examined = readableBuffer.End;
ValidateTlsRequirements();
try
{
if (!readableBuffer.IsEmpty)
{
if (ParsePreface(readableBuffer, out consumed, out examined))
{
break;
}
}
else if (result.IsCompleted)
{
return;
}
}
finally
{
Input.AdvanceTo(consumed, examined);
}
if (!await TryReadPrefaceAsync())
{
return;
}
if (!_stopping)
@ -213,6 +194,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
await _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode);
_frameWriter.Complete();
}
finally
{
@ -222,6 +204,55 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
// https://tools.ietf.org/html/rfc7540#section-9.2
// Some of these could not be checked in advance. Fail before using the connection.
private void ValidateTlsRequirements()
{
var tlsFeature = ConnectionFeatures.Get<ITlsHandshakeFeature>();
if (tlsFeature == null)
{
// Not using TLS at all.
return;
}
if (tlsFeature.Protocol < SslProtocols.Tls12)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorMinTlsVersion(tlsFeature.Protocol), Http2ErrorCode.INADEQUATE_SECURITY);
}
}
private async Task<bool> TryReadPrefaceAsync()
{
while (!_stopping)
{
var result = await Input.ReadAsync();
var readableBuffer = result.Buffer;
var consumed = readableBuffer.Start;
var examined = readableBuffer.End;
try
{
if (!readableBuffer.IsEmpty)
{
if (ParsePreface(readableBuffer, out consumed, out examined))
{
return true;
}
}
if (result.IsCompleted)
{
return false;
}
}
finally
{
Input.AdvanceTo(consumed, examined);
}
}
return false;
}
private bool ParsePreface(ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = readableBuffer.Start;

View File

@ -32,6 +32,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_outputReader = outputPipeReader;
}
public void Complete()
{
lock (_writeLock)
{
if (_completed)
{
return;
}
_completed = true;
_outputWriter.Complete();
}
}
public void Abort(Exception ex)
{
lock (_writeLock)

View File

@ -10,6 +10,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
@ -77,6 +78,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
bool certificateRequired;
var feature = new TlsConnectionFeature();
context.Features.Set<ITlsConnectionFeature>(feature);
context.Features.Set<ITlsHandshakeFeature>(feature);
if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate)
{
@ -210,6 +212,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
#error TFMs need to be updated
#endif
feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate);
feature.CipherAlgorithm = sslStream.CipherAlgorithm;
feature.CipherStrength = sslStream.CipherStrength;
feature.HashAlgorithm = sslStream.HashAlgorithm;
feature.HashStrength = sslStream.HashStrength;
feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm;
feature.KeyExchangeStrength = sslStream.KeyExchangeStrength;
feature.Protocol = sslStream.SslProtocol;
return new HttpsAdaptedConnection(sslStream);
}

View File

@ -2,20 +2,36 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
{
internal class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProtocolFeature
internal class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProtocolFeature, ITlsHandshakeFeature
{
public X509Certificate2 ClientCertificate { get; set; }
public ReadOnlyMemory<byte> ApplicationProtocol { get; set; }
public SslProtocols Protocol { get; set; }
public CipherAlgorithmType CipherAlgorithm { get; set; }
public int CipherStrength { get; set; }
public HashAlgorithmType HashAlgorithm { get; set; }
public int HashStrength { get; set; }
public ExchangeAlgorithmType KeyExchangeAlgorithm { get; set; }
public int KeyExchangeStrength { get; set; }
public Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken)
{
return Task.FromResult(ClientCertificate);

View File

@ -1876,6 +1876,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatHttp2ErrorFrameOverLimit(object size, object limit)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorFrameOverLimit", "size", "limit"), size, limit);
/// <summary>
/// Tls 1.2 or later must be used for HTTP/2. {protocol} was negotiated.
/// </summary>
internal static string Http2ErrorMinTlsVersion
{
get => GetString("Http2ErrorMinTlsVersion");
}
/// <summary>
/// Tls 1.2 or later must be used for HTTP/2. {protocol} was negotiated.
/// </summary>
internal static string FormatHttp2ErrorMinTlsVersion(object protocol)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorMinTlsVersion", "protocol"), protocol);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -89,7 +89,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
writeReq = null;
}
}
else if (result.IsCompleted)
if (result.IsCompleted)
{
break;
}

View File

@ -245,13 +245,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
{
var result = await Output.ReadAsync();
var buffer = result.Buffer;
if (result.IsCanceled)
{
break;
}
var buffer = result.Buffer;
var end = buffer.End;
var isCompleted = result.IsCompleted;
if (!buffer.IsEmpty)

View File

@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Net.Http.Headers;
using Xunit;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
@ -274,6 +275,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_connectionContext = new Http2ConnectionContext
{
ConnectionFeatures = new FeatureCollection(),
ServiceContext = new TestServiceContext()
{
Log = new TestKestrelTrace(_logger)

View File

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NETCOREAPP2_2
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
{
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
[OSSkipCondition(OperatingSystems.Linux, SkipReason = "Curl requires a custom install to support HTTP/2, see https://askubuntu.com/questions/884899/how-do-i-install-curl-with-http2-support")]
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
public class HandshakeTests : LoggedTest
{
private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate();
public HttpClient Client { get; set; }
public HandshakeTests()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// We don't want the default SocketsHttpHandler, it doesn't support HTTP/2 yet.
Client = new HttpClient(new WinHttpHandler()
{
ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
});
}
}
[ConditionalFact]
public async Task TlsAlpnHandshakeSelectsHttp2From1and2()
{
using (var server = new TestServer(context =>
{
var tlsFeature = context.Features.Get<ITlsApplicationProtocolFeature>();
Assert.NotNull(tlsFeature);
Assert.True(SslApplicationProtocol.Http2.Protocol.Span.SequenceEqual(tlsFeature.ApplicationProtocol.Span),
"ALPN: " + tlsFeature.ApplicationProtocol.Length);
return context.Response.WriteAsync("hello world " + context.Request.Protocol);
}, new TestServiceContext(LoggerFactory),
kestrelOptions =>
{
kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps(_x509Certificate2);
});
}))
{
var result = await Client.GetStringAsync($"https://localhost:{server.Port}/");
Assert.Equal("hello world HTTP/2", result);
}
}
[ConditionalFact]
public async Task TlsAlpnHandshakeSelectsHttp2()
{
using (var server = new TestServer(context =>
{
var tlsFeature = context.Features.Get<ITlsApplicationProtocolFeature>();
Assert.NotNull(tlsFeature);
Assert.True(SslApplicationProtocol.Http2.Protocol.Span.SequenceEqual(tlsFeature.ApplicationProtocol.Span),
"ALPN: " + tlsFeature.ApplicationProtocol.Length);
return context.Response.WriteAsync("hello world " + context.Request.Protocol);
}, new TestServiceContext(LoggerFactory),
kestrelOptions =>
{
kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps(_x509Certificate2);
});
}))
{
var result = await Client.GetStringAsync($"https://localhost:{server.Port}/");
Assert.Equal("hello world HTTP/2", result);
}
}
}
}
#elif NET461 // No ALPN support
#else
#error TFMs need updating
#endif

View File

@ -0,0 +1,49 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
namespace System.IO.Pipelines
{
internal class PipeReaderFactory
{
private static readonly Action<object> _cancelReader = state => ((PipeReader)state).CancelPendingRead();
public static PipeReader CreateFromStream(PipeOptions options, Stream stream, CancellationToken cancellationToken)
{
if (!stream.CanRead)
{
throw new NotSupportedException();
}
var pipe = new Pipe(options);
_ = CopyToAsync(stream, pipe, cancellationToken);
return pipe.Reader;
}
private static async Task CopyToAsync(Stream stream, Pipe pipe, CancellationToken cancellationToken)
{
// We manually register for cancellation here in case the Stream implementation ignores it
using (var registration = cancellationToken.Register(_cancelReader, pipe.Reader))
{
try
{
await stream.CopyToAsync(new RawStream(null, pipe.Writer), bufferSize: 4096, cancellationToken);
}
catch (OperationCanceledException)
{
// Ignore the cancellation signal (the pipe reader is already wired up for cancellation when the token trips)
}
catch (Exception ex)
{
pipe.Writer.Complete(ex);
return;
}
pipe.Writer.Complete();
}
}
}
}

View File

@ -0,0 +1,126 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NETCOREAPP2_2
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
{
[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")]
public class TlsTests : LoggedTest
{
private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate();
[ConditionalFact]
public async Task TlsHandshakeRejectsTlsLessThan12()
{
using (var server = new TestServer(context =>
{
var tlsFeature = context.Features.Get<ITlsApplicationProtocolFeature>();
Assert.NotNull(tlsFeature);
Assert.Equal(tlsFeature.ApplicationProtocol, SslApplicationProtocol.Http2.Protocol);
return context.Response.WriteAsync("hello world " + context.Request.Protocol);
}, new TestServiceContext(LoggerFactory),
kestrelOptions =>
{
kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps(_x509Certificate2, httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12;
});
});
}))
{
var connection = server.CreateConnection();
var sslStream = new SslStream(connection.Stream);
await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
{
TargetHost = "localhost",
RemoteCertificateValidationCallback = (_, __, ___, ____) => true,
ApplicationProtocols = new List<SslApplicationProtocol>() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11 },
EnabledSslProtocols = SslProtocols.Tls11, // Intentionally less than the required 1.2
}, CancellationToken.None);
var reader = PipeReaderFactory.CreateFromStream(PipeOptions.Default, sslStream, CancellationToken.None);
await WaitForConnectionErrorAsync(reader, ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.INADEQUATE_SECURITY);
}
}
private async Task WaitForConnectionErrorAsync(PipeReader reader, bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode)
{
var frame = await ReceiveFrameAsync(reader);
if (ignoreNonGoAwayFrames)
{
while (frame.Type != Http2FrameType.GOAWAY)
{
frame = await ReceiveFrameAsync(reader);
}
}
Assert.Equal(Http2FrameType.GOAWAY, frame.Type);
Assert.Equal(8, frame.Length);
Assert.Equal(0, frame.Flags);
Assert.Equal(0, frame.StreamId);
Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId);
Assert.Equal(expectedErrorCode, frame.GoAwayErrorCode);
}
private async Task<Http2Frame> ReceiveFrameAsync(PipeReader reader)
{
var frame = new Http2Frame();
while (true)
{
var result = await reader.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.Start;
var examined = buffer.End;
if (buffer.IsEmpty && result.IsCompleted)
{
throw new IOException("The reader completed without returning a frame.");
}
try
{
// Assert.True(buffer.Length > 0);
if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out consumed, out examined))
{
return frame;
}
}
finally
{
reader.AdvanceTo(consumed, examined);
}
}
}
}
}
#elif NET461 // No ALPN support
#else
#error TFMs need updating
#endif

View File

@ -14,6 +14,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
@ -30,8 +31,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate();
private static X509Certificate2 _x509Certificate2NoExt = TestResources.GetTestCertificate("no_extensions.pfx");
// https://github.com/aspnet/KestrelHttpServer/issues/240
// This test currently fails on mono because of an issue with SslStream.
[Fact]
public async Task CanReadAndWriteWithHttpsConnectionAdapter()
{
@ -55,6 +54,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[Fact]
public async Task HandshakeDetailsAreAvailable()
{
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters =
{
new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 })
}
};
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");
Assert.True(tlsFeature.KeyExchangeStrength >= 0, "KeyExchangeStrength"); // May be 0 on mac
return context.Response.WriteAsync("hello world");
}, new TestServiceContext(LoggerFactory), listenOptions))
{
var result = await HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
Assert.Equal("hello world", result);
}
}
[Fact]
public async Task RequireCertificateFailsWhenNoCertificate()
{

View File

@ -45,15 +45,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions, Action<IServiceCollection> configureServices)
: this(app, context, options => options.ListenOptions.Add(listenOptions), configureServices)
{
}
public TestServer(RequestDelegate app, TestServiceContext context, Action<KestrelServerOptions> configureKestrel)
: this(app, context, configureKestrel, _ => { })
{
}
public TestServer(RequestDelegate app, TestServiceContext context, Action<KestrelServerOptions> configureKestrel, Action<IServiceCollection> configureServices)
{
_app = app;
_listenOptions = listenOptions;
Context = context;
_host = TransportSelector.GetWebHostBuilder()
.UseKestrel(o =>
.UseKestrel(options =>
{
o.ListenOptions.Add(_listenOptions);
configureKestrel(options);
_listenOptions = options.ListenOptions.First();
})
.ConfigureServices(services =>
{
@ -70,7 +79,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
return new KestrelServer(sp.GetRequiredService<ITransportFactory>(), context);
});
RemoveDevCert(services);
configureServices(services);
})
.UseSetting(WebHostDefaults.ApplicationKey, typeof(TestServer).GetTypeInfo().Assembly.FullName)
@ -79,19 +87,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
_host.Start();
}
public static void RemoveDevCert(IServiceCollection services)
{
// KestrelServerOptionsSetup would scan all system certificates on every test server creation
// making test runs very slow
foreach (var descriptor in services.ToArray())
{
if (descriptor.ImplementationType == typeof(KestrelServerOptionsSetup))
{
services.Remove(descriptor);
}
}
}
public IPEndPoint EndPoint => _listenOptions.IPEndPoint;
public int Port => _listenOptions.IPEndPoint.Port;
public AddressFamily AddressFamily => _listenOptions.IPEndPoint.AddressFamily;

View File

@ -27,6 +27,7 @@
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="$(SystemNetHttpWinHttpHandlerPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public static IWebHostBuilder GetWebHostBuilder(Func<MemoryPool<byte>> memoryPoolFactory = null)
{
return new WebHostBuilder().UseLibuv(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; }).ConfigureServices(TestServer.RemoveDevCert);
return new WebHostBuilder().UseLibuv(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; });
}
}
}

View File

@ -26,6 +26,7 @@
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="$(SystemNetHttpWinHttpHandlerPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public static IWebHostBuilder GetWebHostBuilder(Func<MemoryPool<byte>> memoryPoolFactory = null)
{
return new WebHostBuilder().UseSockets(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; }).ConfigureServices(TestServer.RemoveDevCert);
return new WebHostBuilder().UseSockets(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; });
}
}
}