Skip to content

Commit 6338a1c

Browse files
committed
Enable ServerCertificateSelector for HTTP/3 dotnet#34858
1 parent 79aefbb commit 6338a1c

File tree

4 files changed

+174
-5
lines changed

4 files changed

+174
-5
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDe
6666
ApplicationProtocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol("h3"), new SslApplicationProtocol("h3-29") }
6767
};
6868

69+
if (listenOptions.HttpsOptions.ServerCertificateSelector != null)
70+
{
71+
// We can't set both
72+
sslServerAuthenticationOptions.ServerCertificate = null;
73+
sslServerAuthenticationOptions.ServerCertificateSelectionCallback = (sender, host) =>
74+
{
75+
// There is no ConnectionContext available durring the handshake.
76+
return listenOptions.HttpsOptions.ServerCertificateSelector(null!, host)!;
77+
};
78+
}
79+
6980
features.Set(sslServerAuthenticationOptions);
7081
}
7182

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

Lines changed: 5 additions & 2 deletions
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

Lines changed: 32 additions & 3 deletions
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>();
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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.AspNetCore.Server.Kestrel.FunctionalTests;
13+
using Microsoft.AspNetCore.Testing;
14+
using Microsoft.Extensions.DependencyInjection;
15+
using Microsoft.Extensions.Hosting;
16+
using Microsoft.Extensions.Logging;
17+
using Xunit;
18+
19+
namespace Interop.FunctionalTests.Http3
20+
{
21+
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/35070")]
22+
public class Http3TlsTests : LoggedTest
23+
{
24+
[ConditionalFact]
25+
[MsQuicSupported]
26+
public async Task ServerCertificateSelector_Invoked()
27+
{
28+
var builder = CreateHostBuilder(async context =>
29+
{
30+
await context.Response.WriteAsync("Hello World");
31+
}, configureKestrel: kestrelOptions =>
32+
{
33+
kestrelOptions.ListenAnyIP(0, listenOptions =>
34+
{
35+
listenOptions.Protocols = HttpProtocols.Http3;
36+
listenOptions.UseHttps(httpsOptions =>
37+
{
38+
httpsOptions.ServerCertificateSelector = (context, host) =>
39+
{
40+
Assert.Null(context); // The context isn't available durring the quic handshake.
41+
Assert.Equal("localhost", host);
42+
return TestResources.GetTestCertificate();
43+
};
44+
});
45+
});
46+
});
47+
48+
using var host = builder.Build();
49+
using var client = CreateClient();
50+
51+
await host.StartAsync().DefaultTimeout();
52+
53+
// Using localhost instead of 127.0.0.1 because IPs don't set SNI and the Host header isn't currently used as an override.
54+
var request = new HttpRequestMessage(HttpMethod.Get, $"https://localhost:{host.GetPort()}/");
55+
request.Version = HttpVersion.Version30;
56+
request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;
57+
// https://github.com/dotnet/runtime/issues/57169 Host isn't used for SNI
58+
request.Headers.Host = "testhost";
59+
60+
var response = await client.SendAsync(request, CancellationToken.None);
61+
response.EnsureSuccessStatusCode();
62+
var result = await response.Content.ReadAsStringAsync();
63+
Assert.Equal(HttpVersion.Version30, response.Version);
64+
Assert.Equal("Hello World", result);
65+
66+
await host.StopAsync().DefaultTimeout();
67+
}
68+
69+
private static HttpMessageInvoker CreateClient(TimeSpan? idleTimeout = null)
70+
{
71+
var handler = new SocketsHttpHandler();
72+
handler.SslOptions = new System.Net.Security.SslClientAuthenticationOptions
73+
{
74+
RemoteCertificateValidationCallback = (_, __, ___, ____) => true,
75+
TargetHost = "targethost"
76+
};
77+
if (idleTimeout != null)
78+
{
79+
handler.PooledConnectionIdleTimeout = idleTimeout.Value;
80+
}
81+
82+
return new HttpMessageInvoker(handler);
83+
}
84+
85+
private IHostBuilder CreateHostBuilder(RequestDelegate requestDelegate, HttpProtocols? protocol = null, Action<KestrelServerOptions> configureKestrel = null)
86+
{
87+
return new HostBuilder()
88+
.ConfigureWebHost(webHostBuilder =>
89+
{
90+
webHostBuilder
91+
.UseKestrel(o =>
92+
{
93+
if (configureKestrel == null)
94+
{
95+
o.Listen(IPAddress.Parse("127.0.0.1"), 0, listenOptions =>
96+
{
97+
listenOptions.Protocols = protocol ?? HttpProtocols.Http3;
98+
listenOptions.UseHttps();
99+
});
100+
}
101+
else
102+
{
103+
configureKestrel(o);
104+
}
105+
})
106+
.Configure(app =>
107+
{
108+
app.Run(requestDelegate);
109+
});
110+
})
111+
.ConfigureServices(AddTestLogging)
112+
.ConfigureHostOptions(o =>
113+
{
114+
if (Debugger.IsAttached)
115+
{
116+
// Avoid timeout while debugging.
117+
o.ShutdownTimeout = TimeSpan.FromHours(1);
118+
}
119+
else
120+
{
121+
o.ShutdownTimeout = TimeSpan.FromSeconds(1);
122+
}
123+
});
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)