2

I have a working implementation of this but want to make sure it is secure. The goal is to use SSLStream and only accept SSL certificates from the server that are signed by a particular RSA key.

Here is my connection code:

        var client = new TcpClient("server_address", port_number);
        var sslStream = new SslStream(client.GetStream(), false,
            new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
        sslStream.AuthenticateAsClient("SpeechGrid");

And here is my implementation of ValidateServerCertificate:

    private static bool ValidateServerCertificate(object sender, X509Certificate certificate,
            X509Chain chain, SslPolicyErrors sslPolicyErrors) {

        // Only accept our specific key pair
        foreach (var cert in chain.ChainElements) {
            if (cert.Certificate.GetPublicKeyString() == k_prodPublicKey) {
                return true;
            }
        }

        return false;
    }

Because of the richness of the X509Chain object I want to make sure that I don't need to check for things like X509ChainStatusFlags.NotSignatureValid, etc.

For example, would it be possible for an attacker to "claim" to be signed by my public key, send an invalid signature, and this attack would work because .NET assumes I'm checking all of these flags?

Thanks!!

UPDATE: Ok, so far I've decided to put the following checks above the original foreach. Note that this is somewhat application specific; for example if I wanted certificates to expire I would check for NotTimeValid, etc.

        foreach (var status in chain.ChainStatus) {
            switch (status.Status) {
                case X509ChainStatusFlags.Cyclic:
                case X509ChainStatusFlags.NotSignatureValid:
                case X509ChainStatusFlags.PartialChain:
                    return false;
            }
        }
adam smith
  • 764
  • 7
  • 16
  • A **public** key is used for the general public... Only you (the one with the private key) can decrypt the encrypted message, though. If you want to make sure the data is really from a third party, get a public key from them (they will encrypt the data with their key). – Jaroslav Jandek Mar 07 '11 at 21:33

2 Answers2

1

I would reverse the logic of the check you added in the update to your question. Instead of looking for what might be wrong and accepting everything else:

foreach (thing that I can think of that might be wrong)
 return false;

if (public key matches regardless of other policy errors)
 return true;

...I would instead look for what might be wrong yet acceptable, and reject any other policy errors:

if (policy errors)
{
 foreach (error that is acceptable: remote name mismatch, untrusted root, etc.)
   policy errors -= that particular error
}

if (any policy errors left)
 return false;
else if (public key matches)
 return true;
else
 return false;

Something like this for the first part (I did not test or compile this):

if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) == SslPolicyErrors.RemoteCertificateNameMismatch)
{
    sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNameMismatch;
}

if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.RemoteCertificateChainErrors)
{
    var otherFlagsFound =
        from i in chain.ChainStatus
        where (i.Status & ~X509ChainStatusFlags.UntrustedRoot) != X509ChainStatusFlags.NoError
        select i;

    if (otherFlagsFound.Count() == 0)
    {
        sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
    }
}
Jim Flood
  • 8,144
  • 3
  • 36
  • 48
0

You can check the sslPolicyErrors parameter for additional errors such as expired, or if the certificates are not trusted. If everything is ok it should return SslPolicyErrors.None. It is computationally unfeasible to derive a private key from a public key so you don't need to worry about someone else creating the same key pair and signing it.

Can Gencer
  • 8,822
  • 5
  • 33
  • 52
  • Well in my case it shows SslPolicyErrors as "System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch | System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors". Under the chain status it says UntrustedRoot. Note that my certificate is not signed by a CA. So should I just check to make sure that UntrustedRoot is the only chain status? – adam smith Mar 08 '11 at 03:08
  • 3
    RemoteCertificateNameMismatch means that your client is connecting to a hostname that doesn't match what's on the certificate. If you are connecting to "www.myapp.com" then the name on the certificate should match that as well. The untrustedRoot error you get because the certificate is not installed as a Trusted Root CA in the certificate store of the client computer. You can either install that CA in the store or ignore that error as you have another explicit check for the public key. – Can Gencer Mar 08 '11 at 07:39