Pregunta

I've got a server which identifies itself with an ssl certificate. The certificate is self signed.

I want to make sure that the software won't send data, if the user tries to replace server and add "fake" certificate to the list of certificates trusted by the operating system.

Here's what I came up with so far:

ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate;

private static bool ValidateRemoteCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors)
{
    bool result = false;
    if (sender is HttpWebRequest)
    {
        var wr = sender as HttpWebRequest;
        if (wr.Address.Host == "<my host>")
        {
            if (cert == null)
            {
                return false;
            }
            if (cert.GetCertHashString() == "<certificate hash string>")
            {
                return true;
            }
            return false;
        }
    }

    return result;
}

The way I understand the code about, it will trust my certificate for my host, but it won't be protected from host substitution by the user.

I want the software to accept my host only when in identifies itself with the certificate with <certificate hash string>, and if my host presents an other certificate, the connection must be refused.

How do I do that?

¿Fue útil?

Solución

How to make sure that my software is connected to the right server?

Since you know the server in advance, you know both the certificate and the public key the server should be using. To leverage that knowledge, you use a technique called pinning. OWASP has a discussion of it at Certificate and Public Key Pinning.

The code for C# is fairly straight forward. Below is taken from the OWASP page. It connects to Google, and ensures the server is using the expected public key:

// Encoded RSAPublicKey
private static String PUB_KEY = "30818902818100C4A06B7B52F8D17DC1CCB47362" +
    "C64AB799AAE19E245A7559E9CEEC7D8AA4DF07CB0B21FDFD763C63A313A668FE9D764E" +
    "D913C51A676788DB62AF624F422C2F112C1316922AA5D37823CD9F43D1FC54513D14B2" +
    "9E36991F08A042C42EAAEEE5FE8E2CB10167174A359CEBF6FACC2C9CA933AD403137EE" +
    "2C3F4CBED9460129C72B0203010001";

public static void Main(string[] args)
{
  ServicePointManager.ServerCertificateValidationCallback = PinPublicKey;
  WebRequest wr = WebRequest.Create("https://encrypted.google.com/");
  wr.GetResponse();
}

public static bool PinPublicKey(object sender, X509Certificate certificate, X509Chain chain,
                                SslPolicyErrors sslPolicyErrors)
{
  if (null == certificate)
    return false;

  String pk = certificate.GetPublicKeyString();
  if (pk.Equals(PUB_KEY))
    return true;

  // Bad dog
  return false;
}

Certificate pinning does not work with Google and a few others. That's because they rotate their certificate every 30 days or so to keep the CRLs small for mobile clients. But they re-certify the same public key to allow key continuity schemes like pinning to work. So public key pinning will work as expected.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top