Description
Thanks for adding this. We're using
ServerOptionsSelectionCallback
andSslStreamCertificateContext
to enable hosting thousands of domains and selecting the right ssl certificates with Kestrel. ThoughX509Certificate2
on Windows does have temporary file creation issues that don't disappear with dispose that we have to deal with.I had to spend some time to figure this out and to get it working with HTTP/2. Posting some sample code here for the benefit of others trying to use
ServerOptionsSelectionCallback
, for inclusion in documentation, and if anything with the sample code triggers changes/improvements.We needed
ConfigureAlpn
to enable HTTP/2. Maybe this method should be made public inHttpsConnectionMiddleware
or called internally inHttpsConnectionMiddleware
for us?Also would be great to have access to
ConnectionContext
so we can get the connecting ip, listening port, etc. to make certain decisions at this stage or to abort the connection if we didn't find a matching certificate or for any other reasons. If there is a best practice there on how to drop the connection when a matching certificate is not found, would be glad to hear it. Right now, we just return null as the certificate and let Kestrel drop the connection.listenOptions.UseHttps(ServerOptionsSelectionCallback, state: listenOptions.Protocols); ValueTask<SslServerAuthenticationOptions> ServerOptionsSelectionCallback( SslStream stream, SslClientHelloInfo clientHelloInfo, object state, CancellationToken cancellationToken) { var serverOptions = new SslServerAuthenticationOptions { EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13, CertificateRevocationCheckMode = X509RevocationMode.NoCheck, ServerCertificateContext = SslStreamCertificateContext.Create(certificate, intermediateCertificates, offline: true) }; //ConfigureAlpn / Enable Http2 var httpProtocols = (HttpProtocols)state; ConfigureAlpn(serverOptions, httpProtocols); return new ValueTask<SslServerAuthenticationOptions>(serverOptions); } //Copy of method from: //https://github.com/dotnet/aspnetcore/blob/main/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs void ConfigureAlpn(SslServerAuthenticationOptions serverOptions, HttpProtocols httpProtocols) { serverOptions.ApplicationProtocols = new List<SslApplicationProtocol>(); // This is order sensitive if ((httpProtocols & HttpProtocols.Http2) != 0) { serverOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); // https://tools.ietf.org/html/rfc7540#section-9.2.1 serverOptions.AllowRenegotiation = false; } if ((httpProtocols & HttpProtocols.Http1) != 0) { serverOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); } }
Thanks @nitinag for reporting this issue in #25390 (comment).
I think having a public static ConfigureAlpn
method like @nitinag suggests is a reasonable approach. It's internal rather than private today so SniOptionsSelector can call it which probably should have been a sign it should be made public in the first place.
My only concern is that people still won't know to call it and therefore unintentionally disable HTTP/2 when trying to write custom SNI logic. API docs can help here, but not everyone reads it.
I wonder if instead we should automatically populate ApplicationProtocols
if its null. The question there becomes what if I want to disable ALPN? Is that important? Could that be done with an empty list instead of a null list?
We shouldn't let perfect be the enemy of the good though. If we cannot autopupulate the ALPN list for whatever reason, we should just expose ConfigureAlpn
. It shouldn't be necessary for people to copy/paste this code.