I'm doing some tests with .Net 7 preview 7, and the new System.Net.Quic library. I have the following client code:
public static class Program
{
public static async Task Main(string[] args)
{
var serverCert = LoadCert();
var x509policy = new X509ChainPolicy();
// None of the following x509policy lines seem to have any effect
x509policy.TrustMode = X509ChainTrustMode.CustomRootTrust;
x509policy.CustomTrustStore.Add(serverCert);
x509policy.ExtraStore.Add(serverCert);
x509policy.VerificationFlags
= X509VerificationFlags.AllowUnknownCertificateAuthority;
var sslAuthOptions = new SslClientAuthenticationOptions
{
AllowRenegotiation = true,
ApplicationProtocols = new() { SslApplicationProtocol.Http3 },
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13,
EncryptionPolicy = EncryptionPolicy.RequireEncryption,
CertificateChainPolicy = x509policy,
RemoteCertificateValidationCallback = ProcessCertificate,
};
var conectionOptions = new QuicClientConnectionOptions
{
ClientAuthenticationOptions = sslAuthOptions,
RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 15001),
};
var conn = await QuicConnection.ConnectAsync(conectionOptions);
Console.WriteLine(conn.RemoteEndPoint);
// more code here that verifies that data transfer works
}
private static X509Certificate2 LoadCert()
{
var directory = Path
.GetDirectoryName(typeof(Program).Assembly.Location)
?? throw new ArgumentNullException("Assembly.Location");
using var rawCertificate = X509Certificate2
.CreateFromPemFile(Path.Combine(directory, "certificate.pem"));
return new X509Certificate2(
rawCertificate.Export(X509ContentType.Pkcs12));
}
private static bool ProcessCertificate(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
if (chain?.ChainStatus?.Length > 0)
{
var errs = string.Join(
Environment.NewLine,
chain!.ChainStatus
.Select(x => $"[{x.Status}] {x.StatusInformation}"));
Console.WriteLine(errs);
}
return true;
}
}
The certifcate.pem
is a self signed, generated by me certificate. The code above works, meaning I can connect to the server (also written by me). However the ProcessCertificate
callback prints the following error:
[UntrustedRoot] A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.
Note that the client will fail to establish connection without RemoteCertificateValidationCallback
set as above.
Why do I get this error? I added the certificate to X509ChainPolicy
. I've verified manually that what the server returns is the exact same certificate. What am I doing wrong here? Is it possible that System.Net.Quic
does not respect X509ChainPolicy
and I have to manually do the certificate validation in the RemoteCertificateValidationCallback
?
I probably could make it work by adding the certificate to the system wide trusted store. Still I would like to know how to do it without it.
EDIT: I've verified that chain.ChainPolicy
inside the RemoteCertificateValidationCallback
is different from the x509policy
I've just created and set. Is that System.Net.Quic
bug or do I miss something?
Ok, after digging and reading the source code I've found this commit:
https://github.com/dotnet/runtime/commit/1f0582e1ee1e6bf05df41aa4cf0d246584c280c8
which adds X509ChainPolicy
support for Quic. The change is very recent (Aug 4, 2022) and it is not yet in the .Net 7 preview 7 (even though it was released Aug 7, 2022). We'll have to wait for the next release I suppose.