| File: HttpsConfigurationService.cs | Web Access |
| Project: src\aspnetcore\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj (Microsoft.AspNetCore.Server.Kestrel.Core) |
// 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, ILogger<HttpsConnectionMiddleware>>? _populateMultiplexedTransportFeatures; private Func<ListenOptions, ListenOptions>? _useHttpsWithDefaults; private ILogger<HttpsConnectionMiddleware>? _httpsLogger; /// <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 implementation 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; _httpsLogger = httpsLogger; } /// <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, _httpsLogger); } /// <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), nameof(_httpsLogger))] 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); Debug.Assert(_httpsLogger is not null); } /// <summary> /// The initialized implementation of <see cref="PopulateMultiplexedTransportFeatures"/>. /// </summary> internal static void PopulateMultiplexedTransportFeaturesWorker(FeatureCollection features, ListenOptions listenOptions, ILogger<HttpsConnectionMiddleware> logger) { // 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, logger); 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); } } }