Connect client and server via .net quic, http3 and certificates

46 Views Asked by At

I've been trying to get to grips with .net quic for a few days now.

In fact, it works quite well as long as I don't use certificates.

What do I want to do?

  • I want to connect a server to a client
  • SSL protocol runs via Http3
  • I am using .net 8
  • I need certificates for both sides
  • The root certificate for server and client and a separate one for server and client
  • The certificates are self signed for localhost via openSSL

My code looks like this:

[SupportedOSPlatform("windows")]
internal static class Program
{
    // Server
    private static async Task Main()
    {
        var rootCertificate = GetTrustStoreRootCertificate();
        var serverCertificate = GetCertificate();
        
        var chainPolicy = CreateChainPolicy(new X509Certificate2Collection(rootCertificate));

        await using var listener = await QuicListener.ListenAsync(new QuicListenerOptions
        {
            ListenEndPoint = IPEndPoint.Parse("127.0.0.1:8889"),
            ApplicationProtocols = new List<SslApplicationProtocol>
            {
                SslApplicationProtocol.Http3
            },
            ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(new QuicServerConnectionOptions
            {
                DefaultCloseErrorCode = 1,
                DefaultStreamErrorCode = 2,
                IdleTimeout = TimeSpan.FromSeconds(300),
                ServerAuthenticationOptions = new SslServerAuthenticationOptions
                {
                    ApplicationProtocols = new List<SslApplicationProtocol>
                    {
                        SslApplicationProtocol.Http3
                    },
                    ServerCertificate = serverCertificate,
                    ClientCertificateRequired = true,
                    CertificateChainPolicy = chainPolicy,
                    RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
                    {
                        if (errors == SslPolicyErrors.None)
                        {
                            return true;
                        }

                        Console.WriteLine("Certificate error: {0}", errors);

                        return false;
                    },
                }
            }),
        });

        while (true)
        {
            try
            {
                await using var connection = await listener.AcceptConnectionAsync();
                await using var stream = await connection.AcceptInboundStreamAsync();

                // Read
                var buffer = new byte[4096];
                await stream.ReadAsync(buffer);
                Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, buffer.Length));

                // Write
                await stream.WriteAsync(Encoding.UTF8.GetBytes("Pong"));
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }

    private static X509Certificate2 GetCertificate()
    {
        var filePath = Path.Combine(Directory.GetCurrentDirectory(), "server.p12");
        if (!File.Exists(filePath))
        {
            throw new FileNotFoundException("Server certificate not found");
        }
        
        var certificate = new X509Certificate2(filePath, "1412");
        return certificate;
    }

    private static X509Certificate2 GetTrustStoreRootCertificate()
    {
        var filePath = Path.Combine(Directory.GetCurrentDirectory(), "root.p12");
        if (!File.Exists(filePath))
        {
            throw new FileNotFoundException("Root certificate not found");
        }

        var certificate = new X509Certificate2(filePath, "1412");
        return certificate;
    }

    private static X509ChainPolicy CreateChainPolicy(X509Certificate2Collection certificate2Collection)
    {
        var policy = new X509ChainPolicy
        {
            VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority,
            TrustMode = X509ChainTrustMode.CustomRootTrust
        };

        policy.CustomTrustStore.AddRange(certificate2Collection);

        return policy;
    }
}
[SupportedOSPlatform("windows")]
internal static class Program
{
    // Client
    private static async Task Main()
    {
        var rootCertificate = GetTrustStoreRootCertificate();
        var clientCertificate = GetCertificate();

        var chainPolicy = CreateChainPolicy(new X509Certificate2Collection(rootCertificate));

        await using var connection = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions
        {
            RemoteEndPoint = IPEndPoint.Parse("127.0.0.1:8889"),
            DefaultStreamErrorCode = 1,
            DefaultCloseErrorCode = 2,
            ClientAuthenticationOptions = new SslClientAuthenticationOptions
            {
                ApplicationProtocols = new List<SslApplicationProtocol>
                {
                    SslApplicationProtocol.Http3
                },
                ClientCertificates = new X509CertificateCollection { clientCertificate },
                CertificateChainPolicy = chainPolicy,
                TargetHost = "localhost",
                RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
                {
                    if (errors == SslPolicyErrors.None)
                    {
                        return true;
                    }

                    Console.WriteLine("Certificate error: {0}", errors);
                        
                    return false;
                },
            }
        });

        await using var stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
        
        // Write
        await stream.WriteAsync(Encoding.UTF8.GetBytes("Ping"));

        // Read
        var buffer = new byte[4096];
        await stream.ReadAsync(buffer);
        Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, buffer.Length));
        Console.ReadLine();
    }

    private static X509Certificate2 GetCertificate()
    {
        var filePath = Path.Combine(Directory.GetCurrentDirectory(), "client.p12");
        if (!File.Exists(filePath))
        {
            throw new FileNotFoundException("Client certificate not found");
        }
        
        var certificate = new X509Certificate2(filePath, "1412");
        return certificate;
    }

    private static X509Certificate2 GetTrustStoreRootCertificate()
    {
        var filePath = Path.Combine(Directory.GetCurrentDirectory(), "root.p12");
        if (!File.Exists(filePath))
        {
            throw new FileNotFoundException("Root certificate not found");
        }

        var certificate = new X509Certificate2(filePath, "1412");
        return certificate;
    }

    private static X509ChainPolicy CreateChainPolicy(X509Certificate2Collection certificate2Collection)
    {
        var policy = new X509ChainPolicy
        {
            VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority,
            TrustMode = X509ChainTrustMode.CustomRootTrust
        };

        policy.CustomTrustStore.AddRange(certificate2Collection);

        return policy;
    }
}

I get the following error with the client when I try to connect:

System.Security.Authentication.AuthenticationException: 'The remote certificate was rejected by the provided RemoteCertificateValidationCallback.'

   at System.Net.Quic.QuicConnection.SslConnectionOptions.ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER* chainPtr, X509Certificate2& certificate)
   at System.Net.Quic.QuicConnection.HandleEventPeerCertificateReceived(_PEER_CERTIFICATE_RECEIVED_e__Struct& data)
--- End of stack trace from previous location ---
   at System.Net.Quic.ValueTaskSource.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Quic.QuicConnection.<FinishConnectAsync>d__31.MoveNext()
   at System.Net.Quic.QuicConnection.<<ConnectAsync>g__StartConnectAsync|2_0>d.MoveNext()
   at System.Net.Quic.QuicConnection.<<ConnectAsync>g__StartConnectAsync|2_0>d.MoveNext()
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at QuicExample.Client.Program.<Main>d__0.MoveNext() in C:\Tiplu\TipluCloud.Gatekeeper\QuicExample.Client\Program.cs:line 25

I create the certificates as follows with intermediate step certificates:

echo ## Create the root certificate
openssl ecparam -out "Certificates\root.key" -genkey -name secp384r1
openssl req -new -key "Certificates\root.key" -out "Certificates\root-req.pem" -config openssl.cnf
openssl x509 -req -in "Certificates\root-req.pem" -days 3650 -signkey "Certificates\root.key" -out "Certificates\root-cert.crt"

echo ## Create the intermediate server certificate
openssl ecparam -out "Certificates\intermediate-server.key" -genkey -name secp384r1
openssl req -new -key "Certificates\intermediate-server.key" -out "Certificates\intermediate-server-req.pem" -config openssl.cnf
openssl x509 -req -in "Certificates\intermediate-server-req.pem" -days 3650 -CA "Certificates\root-cert.crt" -CAkey "Certificates\root.key" -out "Certificates\intermediate-server-cert.crt"

echo ## Create the server certificate
openssl ecparam -out "Certificates\server.key" -genkey -name secp384r1
openssl req -new -key "Certificates\server.key" -out "Certificates\server-req.pem" -config openssl.cnf
openssl x509 -req -in "Certificates\server-req.pem" -days 3650 -CA "Certificates\intermediate-server-cert.crt" -CAkey "Certificates\intermediate-server.key" -out "Certificates\server-cert.crt"

echo ## Create the intermediate client certificate
openssl ecparam -out "Certificates\intermediate-client.key" -genkey -name secp384r1
openssl req -new -key "Certificates\intermediate-client.key" -out "Certificates\intermediate-client-req.pem" -config openssl.cnf
openssl x509 -req -in "Certificates\intermediate-client-req.pem" -days 3650 -CA "Certificates\root-cert.crt" -CAkey "Certificates\root.key" -out "Certificates\intermediate-client-cert.crt"

echo ## Create the client certificate
openssl ecparam -out "Certificates\client.key" -genkey -name secp384r1
openssl req -new -key "Certificates\client.key" -out "Certificates\client-req.pem" -config openssl.cnf
openssl x509 -req -in "Certificates\client-req.pem" -days 3650 -CA "Certificates\intermediate-client-cert.crt" -CAkey "Certificates\intermediate-client.key" -out "Certificates\client-cert.crt"

echo ## Convert the certificates to PKCS#12 format
openssl pkcs12 -export -in "Certificates\root-cert.crt" -inkey "Certificates\root.key" -out "Certificates\root.p12" -passin pass:1412 -passout pass:1412
openssl pkcs12 -export -in "Certificates\server-cert.crt" -inkey "Certificates\server.key" -out "Certificates\server.p12" -passin pass:1412 -passout pass:1412
openssl pkcs12 -export -in "Certificates\client-cert.crt" -inkey "Certificates\client.key" -out "Certificates\client.p12" -passin pass:1412 -passout pass:1412

openssl.cnf

[ req ]
distinguished_name = subject
default_bits = 4096
req_extensions = req_ext
x509_extensions = x509_ext
prompt = no
encrypt_key = no

default_bits        = 4096
default_md          = sha256
distinguished_name  = subject
req_extensions      = req_ext
x509_extensions     = x509_ext
string_mask         = utf8only
prompt              = no
encrypt_key         = no

[ subject ]
countryName            = EN
stateOrProvinceName    = City123
localityName           = City123
organizationName       = Company123
organizationalUnitName = IT
commonName             = localhost
emailAddress           = [email protected]

[ req_ext ]
subjectKeyIdentifier = hash
basicConstraints     = CA:FALSE
keyUsage             = digitalSignature, keyEncipherment
subjectAltName       = @alternate_names
nsComment            = "Drone-CI - OpenSSL Generated Certificate"

[ x509_ext ]
subjectAltName = @alternate_names
seed = h4e5454aa

[ alternate_names ]
DNS.1 = localhost

Does anyone have any ideas on the best way to solve the problem?` Or does someone see a mistake that I might be overlooking?

0

There are 0 best solutions below