Kestrel HTTP3 endpoint validate server name for client certificate by default
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
### Is there an existing issue for this?
- [x] I have searched the existing issues
### Describe the bug
Kestrel HTTP3 endpoint validate server name for client certificate by default.
### Expected Behavior
Default behavior for HTTP3 endpoint should match default behavior for HTTP2 endpoint, where server name is not validated for client certificates.
### Steps To Reproduce
```csharp
using System;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using var certificateServerLocalhost = X509CertificateLoader.LoadPkcs12(
Convert.FromBase64String(
"MIIDrwIBAzCCA2sGCSqGSIb3DQEHAaCCA1wEggNYMIIDVDCCAW0GCSqGSIb3DQEHAaCCAV4EggFaMIIBVjCCAVIGCyqGSIb3DQEM"
+ "CgECoIHMMIHJMBwGCiqGSIb3DQEMAQMwDgQIad3vtjT4Dl0CAgfQBIGo6KjHd77pQmovty/084x1hiAASWOykB98Ae/UVnk2LpyP"
+ "fMa0CBf9ftYrZoRBxAVCxV69cji5ORmtAHeY4KnUC/YTpNtUSxQMwL+kzs1VVuAn97xlChlwT/Ja8I+nP/SZxqFkAubFSevVICtb"
+ "Ab/AQ2Fm2kc99Cxf3T3w1UXwztrEAia//16oW+lCoJgAWNsmRzDdSyr0DbHKyE0e5+lPYHHe1KI4btQsMXQwEwYJKoZIhvcNAQkV"
+ "MQYEBAEAAAAwXQYJKwYBBAGCNxEBMVAeTgBNAGkAYwByAG8AcwBvAGYAdAAgAFMAbwBmAHQAdwBhAHIAZQAgAEsAZQB5ACAAUwB0"
+ "AG8AcgBhAGcAZQAgAFAAcgBvAHYAaQBkAGUAcjCCAd8GCSqGSIb3DQEHBqCCAdAwggHMAgEAMIIBxQYJKoZIhvcNAQcBMBwGCiqG"
+ "SIb3DQEMAQMwDgQI7CXlOm9fD2gCAgfQgIIBmFjiicG1YQPZbwMya27wyyhreSnznp/WLfycwsa2ssATFgRXssUw49gHs4ZDMqQO"
+ "5y57e4VJoJ4nAb3yQn9HuCqiwv0UJcFc2B70JuRWpWv/TAWgoLuPeeHtOCeBRvhuGW56xkNqiMk/aOQSuOYxKtXhwymY83kZ/g8T"
+ "3uNGRwOkqwzhfrN0vadkBBZmJJ2m7W8cDsWdfFhlFWhN6U9MQK0NnvCqCs9M/jrSfCe103pZb6GiXz6ybW+Ihk/5G5G85DFO2oI/"
+ "Jt++aOBegU+RuyIqsQAhbN13gX0roo5RF5TpQ94QMgxFo46i+Szg0osXggK+unEmCj4xLQ1bTreOaQaSW7RYb1sjm2JbkR66BB7Y"
+ "8YaLZZ79tbQfmzrXiorvOQKkPkDx9qGCRyMluD2kAZ6c5O0X14dc1Bo/RiBHXCpRlJP6tPD1RAF2qwCFtAT3x0/GJyVeAgSfsTvx"
+ "PU6Gv2gFNBtrhnxdhYVTEdDV2w8avJntBdGjO9FQRYhxHXx//RFYeY0CshPQ1lW8TYIxf96KqOWmSGm1TjA7MB8wBwYFKw4DAhoE"
+ "FBBrIxe8fGOgQiTnPTaTYR3QcUwzBBQVujtIjom0GFjf6v9B+43uzBgyLgICB9A="
),
null
);
using var certificateClient = X509CertificateLoader.LoadPkcs12(
Convert.FromBase64String(
"MIIDfwIBAzCCAzsGCSqGSIb3DQEHAaCCAywEggMoMIIDJDCCAW0GCSqGSIb3DQEHAaCCAV4EggFaMIIBVjCCAVIGCyqGSIb3DQEM"
+ "CgECoIHMMIHJMBwGCiqGSIb3DQEMAQMwDgQILlG/36k1fsECAgfQBIGoErtgM6e3Bml4MkwYRvULjwbzguW/VJyCbdk78PcVRVls"
+ "uWVMn6k28gubNFF1mjBw9rZzUD5LYF6mNzVbQMhKbaKDDgzqRf0Gp/rs7oE8s+3Mxa+AJHgdid8ir8FtPcKdQixARRsa/5xgTgS6"
+ "OygWxTDvgfNIToZAIwufEMVkZccm5AYxWsa1mBpU577BYhood7ndlhxf1y77/W+P/8cIrLv3YM7b61uWMXQwEwYJKoZIhvcNAQkV"
+ "MQYEBAEAAAAwXQYJKwYBBAGCNxEBMVAeTgBNAGkAYwByAG8AcwBvAGYAdAAgAFMAbwBmAHQAdwBhAHIAZQAgAEsAZQB5ACAAUwB0"
+ "AG8AcgBhAGcAZQAgAFAAcgBvAHYAaQBkAGUAcjCCAa8GCSqGSIb3DQEHBqCCAaAwggGcAgEAMIIBlQYJKoZIhvcNAQcBMBwGCiqG"
+ "SIb3DQEMAQMwDgQIZj+k/x/0j9QCAgfQgIIBaD7nXwkkq8wWxJAWwqcWmZbpeeGyn2dFr/BgvhW09E6pPtkArJPpcOQjCR8vfMKu"
+ "RtyrkJXHCGhQm/1WhszAKJN7KkKECBWUt++a0h+YkPoaHYyQvy7oZKZ0udD3F3YNAplSB4E05BYF3d3w6PZXuMU/A8t1hLVaP+j6"
+ "2t0q324JvUVmTzQcJ8pOqocSGgUB9T0U5aWj2keJSDM6L4vz3Kkm8YCUEna/QBfp81vVbg6zYXF2BhmRD9Uc/7T9CjoC7aIpRoYT"
+ "Ey8CqxEaMrqq82+f4iSrKY+UmtxCtmVp8SCFcLqz9jTn7aUqwNn9SVRLMMmDSxXaEkRg8NFRPdnepkrF5h2EeIB4Gt+6BzWGbDOV"
+ "Yl8ioGYAS71GKKXNl4snh1woX5AQKVdaM/xohLPd8CiqYE4a8vSP1SSC/s3ttSVKfe3e/ONCV7WxshpYObQW2NAzNH/yi+OdLSY6"
+ "btoeRCkTiF8eulpwNjA7MB8wBwYFKw4DAhoEFL8/AatGW+KGBiEA4LplXG61rBNCBBR7tTmVo7NJZsX91Ysbc4PxyRznUwICB9A="
),
null
);
using var certificateClientLocalhost = X509CertificateLoader.LoadPkcs12(
Convert.FromBase64String(
"MIIDrwIBAzCCA2sGCSqGSIb3DQEHAaCCA1wEggNYMIIDVDCCAW0GCSqGSIb3DQEHAaCCAV4EggFaMIIBVjCCAVIGCyqGSIb3DQEM"
+ "CgECoIHMMIHJMBwGCiqGSIb3DQEMAQMwDgQIKi5n+9Fb2WwCAgfQBIGoEVZtJPlK2py4NO64buCWJiaZkGl1TTkXRUy4yvMTD5GQ"
+ "+v4Qmh3NXF7qtZoElQoA+UexWIyOgkNOdc0q8nPeTwlB9M6WAT8zLltc4u2YJRYABDyqxFHFVMrXyslj1TOogX55GTiXSaVO0D2m"
+ "sARHHI90Qjuf27sxMtBC+1VmpSBEhGZtMO14CHIpz5qRGyigTf/OC7xnpJ95poEKfUh4XdwCbijvot8lMXQwEwYJKoZIhvcNAQkV"
+ "MQYEBAEAAAAwXQYJKwYBBAGCNxEBMVAeTgBNAGkAYwByAG8AcwBvAGYAdAAgAFMAbwBmAHQAdwBhAHIAZQAgAEsAZQB5ACAAUwB0"
+ "AG8AcgBhAGcAZQAgAFAAcgBvAHYAaQBkAGUAcjCCAd8GCSqGSIb3DQEHBqCCAdAwggHMAgEAMIIBxQYJKoZIhvcNAQcBMBwGCiqG"
+ "SIb3DQEMAQMwDgQI3BQzuM21QesCAgfQgIIBmOSvMKdfwj/aVxsahOwtYAUGcpbYCT0SXXEfPy6iLblXBUWHx9Le8xKMpach2lgh"
+ "gcrJ3R2wT8vsREu4l4UT8cqJkl68B3OUhgPfuvso9iVfMgz/To+Sj5i0H4oMGq90cmdqO7bEp0ZOrRu/JNPRqZ1ghVDrnCQYOQ3m"
+ "3gApTnVQaHpDHSY+Nl3ZDy6DnBNZH4NHBqt8KqsKOWwVEtYFfb66Hi9WQBUP3wu8hiNxE+HuYOhdw00Gz1wneYn9xCKqgRKUyOKR"
+ "C0VG7MdWW7RqBK50DvFGGbQcb6ADCUdr8LqtuNqCklfoBd1NG2NOlYzdBQ93w0KMqV5Hd2SyFi+ZEOEjrwYuchk9AxolkVUNbUze"
+ "Y/O8+T30cR022jUi8YliugEhHm0l4huvyjN0M8mQF9dWcq4PxQ/7NUY9CXjXmp5ZLonbavk6iY5nytMaFUnhh3XtRYH6tJLQRZUV"
+ "4p0icUQhWh0g/aTnnSTzSXpOd5td6Mb0khwq+FfGAgrJWGp+LKYTPA6sag5lNpUMqqjYIosnJKQolBS6ADA7MB8wBwYFKw4DAhoE"
+ "FADCiRACrrpEnRFNC09caBMryMWwBBTHIA1DiU5Ni6bAxQ7YglL8CtieBwICB9A="
),
null
);
var builder = WebApplication.CreateEmptyBuilder(new());
builder.WebHost.UseKestrel();
builder
.Services.AddOptions()
.Configure(kestrelOptions =>
{
kestrelOptions.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsOptions.ServerCertificate = certificateServerLocalhost;
httpsOptions.ClientCertificateValidation = (certificate, chain, errors) =>
{
Console.WriteLine($"{certificate.Subject} -> {errors}");
return true;
};
});
kestrelOptions.ListenLocalhost(
12345,
listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2 | HttpProtocols.Http3;
listenOptions.UseHttps();
}
);
});
builder.Services.AddRouting();
using var app = builder.Build();
app.MapGet("/", (HttpRequest request) => request.Protocol);
await app.StartAsync();
using (
HttpClient httpClient = new(
new SocketsHttpHandler()
{
SslOptions =
{
ClientCertificateContext = SslStreamCertificateContext.Create(
certificateClient,
null,
true
),
RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true,
},
}
)
)
{
Console.WriteLine(
await (
await httpClient.SendAsync(
new(HttpMethod.Get, "https://localhost:12345")
{
Version = new(2, 0),
VersionPolicy = HttpVersionPolicy.RequestVersionExact,
}
)
).Content.ReadAsStringAsync()
);
Console.WriteLine(
await (
await httpClient.SendAsync(
new(HttpMethod.Get, "https://localhost:12345")
{
Version = new(3, 0),
VersionPolicy = HttpVersionPolicy.RequestVersionExact,
}
)
).Content.ReadAsStringAsync()
);
}
using (
HttpClient httpClient = new(
new SocketsHttpHandler()
{
SslOptions =
{
ClientCertificateContext = SslStreamCertificateContext.Create(
certificateClientLocalhost,
null,
true
),
RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true,
},
}
)
)
{
Console.WriteLine(
await (
await httpClient.SendAsync(
new(HttpMethod.Get, "https://localhost:12345")
{
Version = new(2, 0),
VersionPolicy = HttpVersionPolicy.RequestVersionExact,
}
)
).Content.ReadAsStringAsync()
);
Console.WriteLine(
await (
await httpClient.SendAsync(
new(HttpMethod.Get, "https://localhost:12345")
{
Version = new(3, 0),
VersionPolicy = HttpVersionPolicy.RequestVersionExact,
}
)
).Content.ReadAsStringAsync()
);
}
await app.StopAsync();
await app.WaitForShutdownAsync();
```
Output:
```
CN=Client -> RemoteCertificateChainErrors
HTTP/2
CN=Client -> RemoteCertificateNameMismatch, RemoteCertificateChainErrors
HTTP/3
CN=Client localhost -> RemoteCertificateChainErrors
HTTP/2
CN=Client localhost -> RemoteCertificateChainErrors
HTTP/3
```
Expected output:
```
CN=Client -> RemoteCertificateChainErrors
HTTP/2
CN=Client -> RemoteCertificateChainErrors
HTTP/3
CN=Client localhost -> RemoteCertificateChainErrors
HTTP/2
CN=Client localhost -> RemoteCertificateChainErrors
HTTP/3
```
### Exceptions (if any)
_No response_
### .NET Version
9.0.203
### Anything else?
```
.NET SDK:
Version: 9.0.203
Commit: dc7acfa194
Workload version: 9.0.200-manifests.9df47798
MSBuild version: 17.13.20+a4ef1e90f
Runtime Environment:
OS Name: Windows
OS Version: 10.0.26100
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\9.0.203\
Host:
Version: 9.0.4
Architecture: x64
Commit: f57e6dc747
.NET SDKs installed:
9.0.203 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 9.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 9.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 9.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
```
Contributor guide
Research direction
Start by running the supplied C# reproduction using Kestrel's ConfigureHttpsDefaults, UseHttps, and HTTP/2/HTTP/3 endpoint. Compare the client-certificate validation output for both protocols and trace the Kestrel HTTP/3 certificate-validation path. Done means HTTP/3 produces the same default server-name validation behavior as HTTP/2 while preserving the shown certificate callback behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100