-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Make TLS & QUIC Pay-for-Play (Redux) #47454
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
7843321
Make TLS & QUIC pay-for-play
amcasey 7aea16b
Improve assert message
amcasey 7b94e52
Adopt MemberNotNullAttribute
amcasey a269b08
Assert that members are non-null to suppress CS8774
amcasey 367d052
UseHttpsConfiguration -> UseKestrelHttpsConfiguration
amcasey 7696f09
UseKestrelSlim -> UseKestrelCore
amcasey d798e05
Drop convenience overloads of UseKestrelCore
amcasey 6236efd
Use more explicit error strings in HttpsConfigurationService.EnsureIn…
amcasey 7d44b2b
Update src/Servers/Kestrel/Core/test/KestrelServerTests.cs
JamesNK File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
260 changes: 260 additions & 0 deletions
260
src/Servers/Kestrel/Core/src/HttpsConfigurationService.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO.Pipelines; | ||
using System.Net; | ||
using System.Net.Security; | ||
using Microsoft.AspNetCore.Connections; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Http.Features; | ||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; | ||
using Microsoft.AspNetCore.Server.Kestrel.Https; | ||
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; | ||
using Microsoft.Extensions.Hosting; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.AspNetCore.Server.Kestrel.Core; | ||
|
||
/// <inheritdoc /> | ||
internal sealed class HttpsConfigurationService : IHttpsConfigurationService | ||
{ | ||
private readonly IInitializer? _initializer; | ||
private bool _isInitialized; | ||
|
||
private TlsConfigurationLoader? _tlsConfigurationLoader; | ||
private Action<FeatureCollection, ListenOptions>? _populateMultiplexedTransportFeatures; | ||
private Func<ListenOptions, ListenOptions>? _useHttpsWithDefaults; | ||
|
||
/// <summary> | ||
/// Create an uninitialized <see cref="HttpsConfigurationService"/>. | ||
/// To initialize it later, call <see cref="Initialize"/>. | ||
/// </summary> | ||
public HttpsConfigurationService() | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Create an initialized <see cref="HttpsConfigurationService"/>. | ||
/// </summary> | ||
/// <remarks> | ||
/// In practice, <see cref="Initialize"/> won't be called until it's needed. | ||
/// </remarks> | ||
public HttpsConfigurationService(IInitializer initializer) | ||
{ | ||
_initializer = initializer; | ||
} | ||
|
||
/// <inheritdoc /> | ||
// If there's an initializer, it *can* be initialized, even though it might not be yet. | ||
// Use explicit interface implentation so we don't accidentally call it within this class. | ||
bool IHttpsConfigurationService.IsInitialized => _isInitialized || _initializer is not null; | ||
|
||
/// <inheritdoc/> | ||
public void Initialize( | ||
IHostEnvironment hostEnvironment, | ||
ILogger<KestrelServer> serverLogger, | ||
ILogger<HttpsConnectionMiddleware> httpsLogger) | ||
{ | ||
if (_isInitialized) | ||
{ | ||
return; | ||
} | ||
|
||
_isInitialized = true; | ||
|
||
_tlsConfigurationLoader = new TlsConfigurationLoader(hostEnvironment, serverLogger, httpsLogger); | ||
_populateMultiplexedTransportFeatures = PopulateMultiplexedTransportFeaturesWorker; | ||
_useHttpsWithDefaults = UseHttpsWithDefaultsWorker; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void ApplyHttpsConfiguration( | ||
HttpsConnectionAdapterOptions httpsOptions, | ||
EndpointConfig endpoint, | ||
KestrelServerOptions serverOptions, | ||
CertificateConfig? defaultCertificateConfig, | ||
ConfigurationReader configurationReader) | ||
{ | ||
EnsureInitialized(CoreStrings.NeedHttpsConfigurationToApplyHttpsConfiguration); | ||
_tlsConfigurationLoader.ApplyHttpsConfiguration(httpsOptions, endpoint, serverOptions, defaultCertificateConfig, configurationReader); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public ListenOptions UseHttpsWithSni(ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions, EndpointConfig endpoint) | ||
{ | ||
// This doesn't get a distinct string since it won't actually throw - it's always called after ApplyHttpsConfiguration | ||
EnsureInitialized(CoreStrings.NeedHttpsConfigurationToApplyHttpsConfiguration); | ||
return _tlsConfigurationLoader.UseHttpsWithSni(listenOptions, httpsOptions, endpoint); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public CertificateAndConfig? LoadDefaultCertificate(ConfigurationReader configurationReader) | ||
{ | ||
EnsureInitialized(CoreStrings.NeedHttpsConfigurationToLoadDefaultCertificate); | ||
return _tlsConfigurationLoader.LoadDefaultCertificate(configurationReader); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void PopulateMultiplexedTransportFeatures(FeatureCollection features, ListenOptions listenOptions) | ||
{ | ||
EnsureInitialized(CoreStrings.NeedHttpsConfigurationToUseHttp3); | ||
_populateMultiplexedTransportFeatures.Invoke(features, listenOptions); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public ListenOptions UseHttpsWithDefaults(ListenOptions listenOptions) | ||
{ | ||
EnsureInitialized(CoreStrings.NeedHttpsConfigurationToBindHttpsAddresses); | ||
return _useHttpsWithDefaults.Invoke(listenOptions); | ||
} | ||
|
||
/// <summary> | ||
/// If this instance has not been initialized, initialize it if possible and throw otherwise. | ||
/// </summary> | ||
/// <exception cref="InvalidOperationException">If initialization is not possible.</exception> | ||
[MemberNotNull(nameof(_useHttpsWithDefaults), nameof(_tlsConfigurationLoader), nameof(_populateMultiplexedTransportFeatures))] | ||
private void EnsureInitialized(string uninitializedError) | ||
{ | ||
if (!_isInitialized) | ||
{ | ||
if (_initializer is not null) | ||
{ | ||
_initializer.Initialize(this); | ||
} | ||
else | ||
{ | ||
throw new InvalidOperationException(uninitializedError); | ||
} | ||
} | ||
|
||
Debug.Assert(_useHttpsWithDefaults is not null); | ||
Debug.Assert(_tlsConfigurationLoader is not null); | ||
Debug.Assert(_populateMultiplexedTransportFeatures is not null); | ||
} | ||
|
||
/// <summary> | ||
/// The initialized implementation of <see cref="PopulateMultiplexedTransportFeatures"/>. | ||
/// </summary> | ||
internal static void PopulateMultiplexedTransportFeaturesWorker(FeatureCollection features, ListenOptions listenOptions) | ||
{ | ||
// HttpsOptions or HttpsCallbackOptions should always be set in production, but it's not set for InMemory tests. | ||
// The QUIC transport will check if TlsConnectionCallbackOptions is missing. | ||
if (listenOptions.HttpsOptions != null) | ||
{ | ||
var sslServerAuthenticationOptions = HttpsConnectionMiddleware.CreateHttp3Options(listenOptions.HttpsOptions); | ||
features.Set(new TlsConnectionCallbackOptions | ||
{ | ||
ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols ?? new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 }, | ||
OnConnection = (context, cancellationToken) => ValueTask.FromResult(sslServerAuthenticationOptions), | ||
OnConnectionState = null, | ||
}); | ||
} | ||
else if (listenOptions.HttpsCallbackOptions != null) | ||
{ | ||
features.Set(new TlsConnectionCallbackOptions | ||
{ | ||
ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 }, | ||
OnConnection = (context, cancellationToken) => | ||
{ | ||
return listenOptions.HttpsCallbackOptions.OnConnection(new TlsHandshakeCallbackContext | ||
{ | ||
ClientHelloInfo = context.ClientHelloInfo, | ||
CancellationToken = cancellationToken, | ||
State = context.State, | ||
Connection = new ConnectionContextAdapter(context.Connection), | ||
}); | ||
}, | ||
OnConnectionState = listenOptions.HttpsCallbackOptions.OnConnectionState, | ||
}); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// The initialized implementation of <see cref="UseHttpsWithDefaults"/>. | ||
/// </summary> | ||
internal static ListenOptions UseHttpsWithDefaultsWorker(ListenOptions listenOptions) | ||
{ | ||
return listenOptions.UseHttps(); | ||
} | ||
|
||
/// <summary> | ||
/// TlsHandshakeCallbackContext.Connection is ConnectionContext but QUIC connection only implements BaseConnectionContext. | ||
/// </summary> | ||
private sealed class ConnectionContextAdapter : ConnectionContext | ||
{ | ||
private readonly BaseConnectionContext _inner; | ||
|
||
public ConnectionContextAdapter(BaseConnectionContext inner) => _inner = inner; | ||
|
||
public override IDuplexPipe Transport | ||
{ | ||
get => throw new NotSupportedException("Not supported by HTTP/3 connections."); | ||
set => throw new NotSupportedException("Not supported by HTTP/3 connections."); | ||
} | ||
public override string ConnectionId | ||
{ | ||
get => _inner.ConnectionId; | ||
set => _inner.ConnectionId = value; | ||
} | ||
public override IFeatureCollection Features => _inner.Features; | ||
public override IDictionary<object, object?> Items | ||
{ | ||
get => _inner.Items; | ||
set => _inner.Items = value; | ||
} | ||
public override EndPoint? LocalEndPoint | ||
{ | ||
get => _inner.LocalEndPoint; | ||
set => _inner.LocalEndPoint = value; | ||
} | ||
public override EndPoint? RemoteEndPoint | ||
{ | ||
get => _inner.RemoteEndPoint; | ||
set => _inner.RemoteEndPoint = value; | ||
} | ||
public override CancellationToken ConnectionClosed | ||
{ | ||
get => _inner.ConnectionClosed; | ||
set => _inner.ConnectionClosed = value; | ||
} | ||
public override ValueTask DisposeAsync() => _inner.DisposeAsync(); | ||
} | ||
|
||
/// <summary> | ||
/// Register an instance of this type to initialize registered instances of <see cref="HttpsConfigurationService"/>. | ||
/// </summary> | ||
internal interface IInitializer | ||
{ | ||
/// <summary> | ||
/// Invokes <see cref="IHttpsConfigurationService.Initialize"/>, passing appropriate arguments. | ||
/// </summary> | ||
void Initialize(IHttpsConfigurationService httpsConfigurationService); | ||
} | ||
|
||
/// <inheritdoc/> | ||
internal sealed class Initializer : IInitializer | ||
{ | ||
private readonly IHostEnvironment _hostEnvironment; | ||
private readonly ILogger<KestrelServer> _serverLogger; | ||
private readonly ILogger<HttpsConnectionMiddleware> _httpsLogger; | ||
|
||
public Initializer( | ||
IHostEnvironment hostEnvironment, | ||
ILogger<KestrelServer> serverLogger, | ||
ILogger<HttpsConnectionMiddleware> httpsLogger) | ||
{ | ||
_hostEnvironment = hostEnvironment; | ||
_serverLogger = serverLogger; | ||
_httpsLogger = httpsLogger; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void Initialize(IHttpsConfigurationService httpsConfigurationService) | ||
{ | ||
httpsConfigurationService.Initialize(_hostEnvironment, _serverLogger, _httpsLogger); | ||
} | ||
} | ||
} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.