Thursday, March 8, 2012

ServicePointManager.ServerCertificateValidationCallback - the magic cure

The things I am going to touch in this article are widely described. It is a common task for a developer to test WCF services on the development environment having transport security enabled for a service. So basically what is done here:

1) IIS is configured to enable SSL for a web site, hosting a WCF service.
    Instructions for IIS 6.0 can be found here.
    Instructions for IIS 7.0 can be found here.
2) Self issued certificate is being used for that purpose.
    Instructions for creating self issued certificate can be found here.
3) WCF service and client bindings are configured to have transport security enabled.
    Instructions to enable WCF transport security can be found here. 

After spending couple of days on the steps mentioned above sometimes you might come to a glorious moment when you feel strong enough to test your service. After doing that you might see something like this:

System.ServiceModel.Security.SecurityNegotiationException: Could not establish trust relationship for the SSL/TLS secure channel with authority 'xxx.xxx.xxx'. 
---> System.Net.WebException: The underlying connection was closed: Could not
establish trust relationship for the SSL/TLS secure channel. 
---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.

It is actually saying that your certificate cannot be validated. You might try solving this problem by adding the server certificate to some trusted authority certificate store on your local machine. But my guess is that 99% would solve this issue like this:

ServicePointManager.ServerCertificateValidationCallback += 
   (sender, certificate, chain, sslPolicyErrors) => true;

What happens then is that you have your client and server talking to each other using a secured channel. Nice, isn't it? Make sure it is for debugging purposes only.

What if you have a client, talking to multiple services hosted by different service providers? This could be a real world scenario, i.e. an integration module talking to multiple B2B systems.

Overriding ServicePointManager.ServerCertificateValidationCallback behaviour would turn off server certificate validation globally for the whole client application. This is a problem if you want your certificate validation to be shut off for a single service only.

It is worth to take a quick look into the place where ServicePointManager.ServerCertificateValidationCallback is used in the .NET framework infrastructure. To be more specific: look for a HandshakeDoneProcedure.CertValidationCallback method. This is a private class so most probably Reflector is the tool to help you here. Or let me share a snippet:

private bool CertValidationCallback(string hostName, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    ...
    bool flag = true;
    ...
    if (ServicePointManager.ServerCertificateValidationCallback != null)
    {
        flag = false;
        return ServicePointManager.ServerCertValidationCallback.Invoke(this.m_Request, certificate, chain, sslPolicyErrors);
    }
    if (flag)
    {
        return (sslPolicyErrors == SslPolicyErrors.None);
    }
    return true;
}

Default certificate validation behaviour (when validation callback is not assigned) consists of validating sslPolicyErrors value. You might want to do the same for the services you still need server certificates to be validated.

ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(ValidateCert);

public static bool ValidateCert(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    HttpWebRequest request = sender as HttpWebRequest;

    if (request != null && request.Host == "host_name_to_skip_validation")
    {
        return true;
    }

    return sslPolicyErrors == SslPolicyErrors.None;
}

No comments:

Post a Comment