Skip to content

Commit 566d793

Browse files
committed
Flow SNI HttpProtocols to middleware
1 parent 5e125f3 commit 566d793

File tree

6 files changed

+93
-82
lines changed

6 files changed

+93
-82
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
5+
{
6+
internal class HttpProtocolsFeature
7+
{
8+
public HttpProtocolsFeature(HttpProtocols httpProtocols)
9+
{
10+
HttpProtocols = httpProtocols;
11+
}
12+
13+
public HttpProtocols HttpProtocols { get; }
14+
}
15+
}

src/Servers/Kestrel/Core/src/Internal/SniOptions.cs

Lines changed: 0 additions & 36 deletions
This file was deleted.

src/Servers/Kestrel/Core/src/Internal/SniOptionsSelector.cs

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7+
using System.Linq;
78
using System.Net.Security;
89
using System.Security.Authentication;
910
using System.Security.Cryptography.X509Certificates;
@@ -42,13 +43,13 @@ public SniOptionsSelector(
4243

4344
foreach (var (name, sniConfig) in endpointConfig.SNI)
4445
{
45-
var sslServerOptions = new SslServerAuthenticationOptions
46+
var sslOptions = new SslServerAuthenticationOptions
4647
{
4748
ServerCertificate = configLoader.LoadCertificate(sniConfig.Certificate, endpointConfig.Name),
4849
EnabledSslProtocols = sniConfig.SslProtocols ?? fallbackOptions.SslProtocols,
4950
};
5051

51-
if (sslServerOptions.ServerCertificate is null)
52+
if (sslOptions.ServerCertificate is null)
5253
{
5354
if (fallbackOptions.ServerCertificate is null && fallbackOptions.ServerCertificateSelector is null)
5455
{
@@ -58,27 +59,27 @@ public SniOptionsSelector(
5859
if (fallbackOptions.ServerCertificateSelector is null)
5960
{
6061
// Cache the fallback ServerCertificate since there's no fallback ServerCertificateSelector taking precedence.
61-
sslServerOptions.ServerCertificate = fallbackOptions.ServerCertificate;
62+
sslOptions.ServerCertificate = fallbackOptions.ServerCertificate;
6263
}
6364
}
6465

6566
var clientCertificateMode = sniConfig.ClientCertificateMode ?? fallbackOptions.ClientCertificateMode;
6667

6768
if (clientCertificateMode != ClientCertificateMode.NoCertificate)
6869
{
69-
sslServerOptions.ClientCertificateRequired = true;
70-
sslServerOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
70+
sslOptions.ClientCertificateRequired = true;
71+
sslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
7172
HttpsConnectionMiddleware.RemoteCertificateValidationCallback(
7273
clientCertificateMode, fallbackOptions.ClientCertificateValidation, certificate, chain, sslPolicyErrors);
7374
}
7475

7576
var httpProtocols = sniConfig.Protocols ?? fallbackHttpProtocols;
7677
httpProtocols = HttpsConnectionMiddleware.ValidateAndNormalizeHttpProtocols(httpProtocols, logger);
77-
HttpsConnectionMiddleware.ConfigureAlpn(sslServerOptions, httpProtocols);
78+
HttpsConnectionMiddleware.ConfigureAlpn(sslOptions, httpProtocols);
7879

7980
var sniOptions = new SniOptions
8081
{
81-
SslOptions = sslServerOptions,
82+
SslOptions = sslOptions,
8283
HttpProtocols = httpProtocols,
8384
};
8485

@@ -97,37 +98,19 @@ public SniOptionsSelector(
9798
}
9899
}
99100

100-
public SniOptions GetOptions(ConnectionContext connection, string serverName)
101+
public SslServerAuthenticationOptions GetOptions(ConnectionContext connection, string serverName)
101102
{
102-
SniOptions options = null;
103+
SniOptions sniOptions = null;
103104

104-
if (!string.IsNullOrEmpty(serverName))
105+
if (!string.IsNullOrEmpty(serverName) && !_fullNameOptions.TryGetValue(serverName, out sniOptions))
105106
{
106-
if (_fullNameOptions.TryGetValue(serverName, out options))
107-
{
108-
return options;
109-
}
110-
111-
var matchedNameLength = 0;
112-
ReadOnlySpan<char> serverNameSpan = serverName;
113-
114-
foreach (var (nameCandidate, optionsCandidate) in _wildcardPrefixOptions)
115-
{
116-
ReadOnlySpan<char> nameCandidateSpan = nameCandidate;
117-
118-
// Note that we only slice off the `*`. We want to match the leading `.` also.
119-
if (serverNameSpan.EndsWith(nameCandidateSpan.Slice(wildcardHost.Length), StringComparison.OrdinalIgnoreCase) &&
120-
nameCandidateSpan.Length > matchedNameLength)
121-
{
122-
matchedNameLength = nameCandidateSpan.Length;
123-
options = optionsCandidate;
124-
}
125-
}
107+
TryGetWildcardPrefixedOptions(serverName, out sniOptions);
126108
}
127109

128-
options ??= _wildcardHostOptions;
110+
// Fully wildcarded ("*") options can be used even when given an empty server name.
111+
sniOptions ??= _wildcardHostOptions;
129112

130-
if (options is null)
113+
if (sniOptions is null)
131114
{
132115
if (serverName is null)
133116
{
@@ -139,25 +122,75 @@ public SniOptions GetOptions(ConnectionContext connection, string serverName)
139122
}
140123
}
141124

142-
if (options.SslOptions.ServerCertificate is null)
125+
connection.Features.Set(new HttpProtocolsFeature(sniOptions.HttpProtocols));
126+
127+
var sslOptions = sniOptions.SslOptions;
128+
129+
if (sslOptions.ServerCertificate is null)
143130
{
144131
Debug.Assert(_fallbackServerCertificateSelector != null,
145132
"The cached SniOptions ServerCertificate can only be null if there's a fallback certificate selector.");
146133

147134
// If a ServerCertificateSelector doesn't return a cert, HttpsConnectionMiddleware doesn't fallback to the ServerCertificate.
148-
options = options.Clone();
149-
options.SslOptions.ServerCertificate = _fallbackServerCertificateSelector(connection, serverName);
135+
sslOptions = CloneSslOptions(sslOptions);
136+
sslOptions.ServerCertificate = _fallbackServerCertificateSelector(connection, serverName);
150137
}
151138

152139
if (_onAuthenticateCallback != null)
153140
{
154-
options = options.Clone();
141+
sslOptions = CloneSslOptions(sslOptions);
155142

156143
// From doc comments: "This is called after all of the other settings have already been applied."
157-
_onAuthenticateCallback(connection, options.SslOptions);
158-
}
144+
_onAuthenticateCallback(connection, sslOptions);
145+
}
146+
147+
return sslOptions;
148+
}
159149

160-
return options;
150+
private bool TryGetWildcardPrefixedOptions(string serverName, out SniOptions sniOptions)
151+
{
152+
sniOptions = null;
153+
154+
var matchedNameLength = 0;
155+
ReadOnlySpan<char> serverNameSpan = serverName;
156+
157+
foreach (var (nameCandidate, optionsCandidate) in _wildcardPrefixOptions)
158+
{
159+
ReadOnlySpan<char> nameCandidateSpan = nameCandidate;
160+
161+
// Note that we only slice off the `*`. We want to match the leading `.` also.
162+
if (serverNameSpan.EndsWith(nameCandidateSpan.Slice(wildcardHost.Length), StringComparison.OrdinalIgnoreCase) &&
163+
nameCandidateSpan.Length > matchedNameLength)
164+
{
165+
matchedNameLength = nameCandidateSpan.Length;
166+
sniOptions = optionsCandidate;
167+
}
168+
}
169+
170+
return sniOptions != null;
171+
}
172+
173+
// TODO: Reflection based test to ensure we clone everything!
174+
internal static SslServerAuthenticationOptions CloneSslOptions(SslServerAuthenticationOptions sslOptions) =>
175+
new SslServerAuthenticationOptions
176+
{
177+
AllowRenegotiation = sslOptions.AllowRenegotiation,
178+
ApplicationProtocols = sslOptions.ApplicationProtocols?.ToList(),
179+
CertificateRevocationCheckMode = sslOptions.CertificateRevocationCheckMode,
180+
CipherSuitesPolicy = sslOptions.CipherSuitesPolicy,
181+
ClientCertificateRequired = sslOptions.ClientCertificateRequired,
182+
EnabledSslProtocols = sslOptions.EnabledSslProtocols,
183+
EncryptionPolicy = sslOptions.EncryptionPolicy,
184+
RemoteCertificateValidationCallback = sslOptions.RemoteCertificateValidationCallback,
185+
ServerCertificate = sslOptions.ServerCertificate,
186+
ServerCertificateContext = sslOptions.ServerCertificateContext,
187+
ServerCertificateSelectionCallback = sslOptions.ServerCertificateSelectionCallback,
188+
};
189+
190+
private class SniOptions
191+
{
192+
public SslServerAuthenticationOptions SslOptions { get; set; }
193+
public HttpProtocols HttpProtocols { get; set; }
161194
}
162195
}
163196
}

src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ public void Load()
359359
var logger = Options.ApplicationServices.GetRequiredService<ILogger<KestrelConfigurationLoader>>();
360360
var sniOptionsSelector = new SniOptionsSelector(this, endpoint, httpsOptions, listenOptions.Protocols, logger);
361361

362-
listenOptions.UseHttps(ServerOptionsSelectionCallback, sniOptionsSelector, httpsOptions.HandshakeTimeout);
362+
listenOptions.UseHttps(SniCallback, sniOptionsSelector, httpsOptions.HandshakeTimeout);
363363
}
364364
}
365365

@@ -372,11 +372,11 @@ public void Load()
372372
return (endpointsToStop, endpointsToStart);
373373
}
374374

375-
private static ValueTask<SslServerAuthenticationOptions> ServerOptionsSelectionCallback(ConnectionContext connection, SslStream stream, SslClientHelloInfo clientHelloInfo, object state, CancellationToken cancellationToken)
375+
private static ValueTask<SslServerAuthenticationOptions> SniCallback(ConnectionContext connection, SslStream stream, SslClientHelloInfo clientHelloInfo, object state, CancellationToken cancellationToken)
376376
{
377377
var sniOptionsSelector = (SniOptionsSelector)state;
378378
var options = sniOptionsSelector.GetOptions(connection, clientHelloInfo.ServerName);
379-
return new ValueTask<SslServerAuthenticationOptions>(options.SslOptions);
379+
return new ValueTask<SslServerAuthenticationOptions>(options);
380380
}
381381

382382
private void LoadDefaultCert(ConfigurationReader configReader)

src/Servers/Kestrel/Core/src/Middleware/HttpConnectionMiddleware.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ internal class HttpConnectionMiddleware<TContext>
1313
{
1414
private readonly ServiceContext _serviceContext;
1515
private readonly IHttpApplication<TContext> _application;
16-
private readonly HttpProtocols _protocols;
16+
private readonly HttpProtocols _endpointDefaultProtocols;
1717

1818
public HttpConnectionMiddleware(ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols)
1919
{
2020
_serviceContext = serviceContext;
2121
_application = application;
22-
_protocols = protocols;
22+
_endpointDefaultProtocols = protocols;
2323
}
2424

2525
public Task OnConnectionAsync(ConnectionContext connectionContext)
@@ -30,7 +30,7 @@ public Task OnConnectionAsync(ConnectionContext connectionContext)
3030
{
3131
ConnectionId = connectionContext.ConnectionId,
3232
ConnectionContext = connectionContext,
33-
Protocols = _protocols,
33+
Protocols = connectionContext.Features.Get<HttpProtocolsFeature>()?.HttpProtocols ?? _endpointDefaultProtocols,
3434
ServiceContext = _serviceContext,
3535
ConnectionFeatures = connectionContext.Features,
3636
MemoryPool = memoryPoolFeature?.MemoryPool ?? System.Buffers.MemoryPool<byte>.Shared,

src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
2626
{
27-
2827
internal delegate ValueTask<SslServerAuthenticationOptions> HttpsOptionsCallback(ConnectionContext connection, SslStream stream, SslClientHelloInfo clientHelloInfo, object state, CancellationToken cancellationToken);
2928

3029
internal class HttpsConnectionMiddleware

0 commit comments

Comments
 (0)