Search code examples
c#quic.net-7.0

How to trust a self-signed certificate with System.Net.Quic?


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?


Solution

  • 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.