Skip to content

Commit a671f96

Browse files
authored
Enable ServerCertificateSelector for HTTP/3 dotnet#34858 (dotnet#35243)
1 parent 8ecc77a commit a671f96

File tree

8 files changed

+221
-78
lines changed

8 files changed

+221
-78
lines changed

src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ public HttpsConnectionAdapterOptions()
4242
/// <summary>
4343
/// <para>
4444
/// A callback that will be invoked to dynamically select a server certificate. This is higher priority than ServerCertificate.
45-
/// If SNI is not available then the name parameter will be null.
45+
/// If SNI is not available then the name parameter will be null. The <see cref="ConnectionContext"/> will be null for HTTP/3 connections.
4646
/// </para>
4747
/// <para>
4848
/// If the server certificate has an Extended Key Usage extension, the usages must include Server Authentication (OID 1.3.6.1.5.5.7.3.1).
4949
/// </para>
5050
/// </summary>
51-
public Func<ConnectionContext, string?, X509Certificate2?>? ServerCertificateSelector { get; set; }
51+
public Func<ConnectionContext?, string?, X509Certificate2?>? ServerCertificateSelector { get; set; }
5252

5353
/// <summary>
5454
/// Specifies the client certificate requirements for a HTTPS connection. Defaults to <see cref="ClientCertificateMode.NoCertificate"/>.

src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs

+17
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Threading.Tasks;
1313
using Microsoft.AspNetCore.Connections;
1414
using Microsoft.AspNetCore.Http.Features;
15+
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
1516

1617
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
1718
{
@@ -66,6 +67,22 @@ public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDe
6667
ApplicationProtocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol("h3"), new SslApplicationProtocol("h3-29") }
6768
};
6869

70+
if (listenOptions.HttpsOptions.ServerCertificateSelector != null)
71+
{
72+
// We can't set both
73+
sslServerAuthenticationOptions.ServerCertificate = null;
74+
sslServerAuthenticationOptions.ServerCertificateSelectionCallback = (sender, host) =>
75+
{
76+
// There is no ConnectionContext available durring the QUIC handshake.
77+
var cert = listenOptions.HttpsOptions.ServerCertificateSelector(null, host);
78+
if (cert != null)
79+
{
80+
HttpsConnectionMiddleware.EnsureCertificateIsAllowedForServerAuth(cert);
81+
}
82+
return cert!;
83+
};
84+
}
85+
6986
features.Set(sslServerAuthenticationOptions);
7087
}
7188

src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions.OnAuthen
183183
Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions.OnAuthenticate.set -> void
184184
Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions.ServerCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2?
185185
Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions.ServerCertificate.set -> void
186-
Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions.ServerCertificateSelector.get -> System.Func<Microsoft.AspNetCore.Connections.ConnectionContext!, string?, System.Security.Cryptography.X509Certificates.X509Certificate2?>?
186+
Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions.ServerCertificateSelector.get -> System.Func<Microsoft.AspNetCore.Connections.ConnectionContext?, string?, System.Security.Cryptography.X509Certificates.X509Certificate2?>?
187187
Microsoft.AspNetCore.Server.Kestrel.Https.HttpsConnectionAdapterOptions.ServerCertificateSelector.set -> void
188188
Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.AnyIPEndpoint(int port) -> Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader!
189189
Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.AnyIPEndpoint(int port, System.Action<Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions!>! configure) -> Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader!

src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,12 @@ public ValueTask<IMultiplexedConnectionListener> BindAsync(EndPoint endpoint, IF
5454
{
5555
throw new InvalidOperationException("Couldn't find HTTPS configuration for QUIC transport.");
5656
}
57-
if (sslServerAuthenticationOptions.ServerCertificate == null)
57+
if (sslServerAuthenticationOptions.ServerCertificate == null
58+
&& sslServerAuthenticationOptions.ServerCertificateContext == null
59+
&& sslServerAuthenticationOptions.ServerCertificateSelectionCallback == null)
5860
{
59-
var message = $"{nameof(SslServerAuthenticationOptions)}.{nameof(SslServerAuthenticationOptions.ServerCertificate)} must be configured with a value.";
61+
var message = $"{nameof(SslServerAuthenticationOptions)} must provide a server certificate using {nameof(SslServerAuthenticationOptions.ServerCertificate)},"
62+
+ $" {nameof(SslServerAuthenticationOptions.ServerCertificateContext)}, or {nameof(SslServerAuthenticationOptions.ServerCertificateSelectionCallback)}.";
6063
throw new InvalidOperationException(message);
6164
}
6265

src/Servers/Kestrel/samples/Http3SampleApp/Program.cs

+32-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public static void Main(string[] args)
2525
.ConfigureKestrel((context, options) =>
2626
{
2727
var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false);
28+
2829
options.ConfigureHttpsDefaults(httpsOptions =>
2930
{
3031
httpsOptions.ServerCertificate = cert;
@@ -54,13 +55,41 @@ public static void Main(string[] args)
5455
{
5556
listenOptions.UseHttps(httpsOptions =>
5657
{
57-
httpsOptions.ServerCertificateSelector = (_, _) => cert;
58+
// ConnectionContext is null
59+
httpsOptions.ServerCertificateSelector = (context, host) => cert;
5860
});
5961
listenOptions.UseConnectionLogging();
60-
listenOptions.Protocols = HttpProtocols.Http1AndHttp2; // TODO: http3
62+
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
6163
});
6264

65+
// No SslServerAuthenticationOptions callback is currently supported by QuicListener
6366
options.ListenAnyIP(5004, listenOptions =>
67+
{
68+
listenOptions.UseHttps(httpsOptions =>
69+
{
70+
httpsOptions.OnAuthenticate = (_, sslOptions) => sslOptions.ServerCertificate = cert;
71+
});
72+
listenOptions.UseConnectionLogging();
73+
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
74+
});
75+
76+
// ServerOptionsSelectionCallback isn't currently supported by QuicListener
77+
options.ListenAnyIP(5005, listenOptions =>
78+
{
79+
ServerOptionsSelectionCallback callback = (SslStream stream, SslClientHelloInfo clientHelloInfo, object state, CancellationToken cancellationToken) =>
80+
{
81+
var options = new SslServerAuthenticationOptions()
82+
{
83+
ServerCertificate = cert,
84+
};
85+
return new ValueTask<SslServerAuthenticationOptions>(options);
86+
};
87+
listenOptions.UseHttps(callback, state: null);
88+
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
89+
});
90+
91+
// TlsHandshakeCallbackOptions (ServerOptionsSelectionCallback) isn't currently supported by QuicListener
92+
options.ListenAnyIP(5006, listenOptions =>
6493
{
6594
listenOptions.UseHttps(new TlsHandshakeCallbackOptions()
6695
{
@@ -74,7 +103,7 @@ public static void Main(string[] args)
74103
},
75104
});
76105
listenOptions.UseConnectionLogging();
77-
listenOptions.Protocols = HttpProtocols.Http1AndHttp2; // TODO: http3
106+
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
78107
});
79108
})
80109
.UseStartup<Startup>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Net;
6+
using System.Net.Http;
7+
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.AspNetCore.Connections;
9+
using Microsoft.AspNetCore.Hosting;
10+
using Microsoft.AspNetCore.Http;
11+
using Microsoft.AspNetCore.Server.Kestrel.Core;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Hosting;
14+
using Microsoft.Extensions.Logging;
15+
16+
namespace Interop.FunctionalTests.Http3
17+
{
18+
public static class Http3Helpers
19+
{
20+
public static HttpMessageInvoker CreateClient(TimeSpan? idleTimeout = null)
21+
{
22+
var handler = new SocketsHttpHandler();
23+
handler.SslOptions = new System.Net.Security.SslClientAuthenticationOptions
24+
{
25+
RemoteCertificateValidationCallback = (_, __, ___, ____) => true,
26+
TargetHost = "targethost"
27+
};
28+
if (idleTimeout != null)
29+
{
30+
handler.PooledConnectionIdleTimeout = idleTimeout.Value;
31+
}
32+
33+
return new HttpMessageInvoker(handler);
34+
}
35+
36+
public static IHostBuilder CreateHostBuilder(Action<IServiceCollection> configureServices, RequestDelegate requestDelegate, HttpProtocols? protocol = null, Action<KestrelServerOptions> configureKestrel = null)
37+
{
38+
return new HostBuilder()
39+
.ConfigureWebHost(webHostBuilder =>
40+
{
41+
webHostBuilder
42+
.UseKestrel(o =>
43+
{
44+
if (configureKestrel == null)
45+
{
46+
o.Listen(IPAddress.Parse("127.0.0.1"), 0, listenOptions =>
47+
{
48+
listenOptions.Protocols = protocol ?? HttpProtocols.Http3;
49+
listenOptions.UseHttps();
50+
});
51+
}
52+
else
53+
{
54+
configureKestrel(o);
55+
}
56+
})
57+
.Configure(app =>
58+
{
59+
app.Run(requestDelegate);
60+
});
61+
})
62+
.ConfigureServices(configureServices)
63+
.ConfigureHostOptions(o =>
64+
{
65+
if (Debugger.IsAttached)
66+
{
67+
// Avoid timeout while debugging.
68+
o.ShutdownTimeout = TimeSpan.FromHours(1);
69+
}
70+
else
71+
{
72+
o.ShutdownTimeout = TimeSpan.FromSeconds(1);
73+
}
74+
});
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)