SslStream AuthenticateAsServer with optional ClientCertificate

2.9k views Asked by At

Considering the SslStream.AuthenticateAsServer method, the second parameter clientCertificateRequired:

If it is set to true, a client certificate is required. It will throw an exception if not. The client certificate will be available in the property RemoteCertificate.

When set to false, no client certificate is required, the property RemoteCertificate shall always be null. Even if one is provided by the client.

What I like to accomplish is to let the client decide whether it will or will not provide a certificate. But, if they do provide one, I like to know it on the server.

I have tried to first set the variable to true, if this fails, fallback to not require the certificat. However, this result in an "Already Authenticated Exception".

try{
        sslStream.AuthenticateAsServer(x509certificate, true, SslProtocols.Tls, true);
}catch(Exception ex){
        sslStream.AuthenticateAsServer(x509certificate, false, SslProtocols.Tls, true);
}
1

There are 1 answers

1
Dio F On BEST ANSWER

I strongly believe this is a documentation flaw.

Actually the parameter clientCertificateRequired will control whether client certificates are not ignored. This means:

clientCertificateRequired = false will ignore any client certificate on the server side. No certificate is checked for existence nor validity.

clientCertificateRequired = true will respect any sent client certificate on the server side. If a client certificate is missing the validation callback is called with SslPolicyErrors.RemoteCertificateNotAvailable which results in the exception you catch, when using the default implementation.

So in your case: Set clientCertificateRequired to true and implement a custom validation callback, like this:

var client = server.AcceptTcpClient()
var networkStream = client.GetStream()

var sslStream = new SslStream(
    networkStream,
    false,
    (sender, certificate, chain, errors) =>
    {
        if (errors == SslPolicyErrors.None)
        {
            return true;
        }

        if (errors == SslPolicyErrors.RemoteCertificateNotAvailable)
        {
            // seems to be okay for you
            return true;
        }

        return false;
    },
    (sender, host, certificates, certificate, issuers) => x509certificate
);

sslStream.AuthenticateAsServer(x509certificate, true, SslProtocols.Tls, true);