Showing posts with label SSL. Show all posts
Showing posts with label SSL. Show all posts

Friday, March 9, 2012

Solving "Received an unexpected EOF or 0 bytes from the transport stream" issue

One of the popular sayings appears to be correct when saying that most of the things are not so easy as they seem to be. Calling a web service with transport security enabled - this is what I'd expect to be easy. And it does... until something similar shows up: 

System.ServiceModel.CommunicationException: An error occurred while making the HTTP request to https://xxx.xxx.xxx. 
This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. 
This could also be caused by a mismatch of the security binding between the client and the server. 
---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. 
---> System.IO.IOException: Received an unexpected EOF or 0 bytes from the transport stream.
   
My first idea was that the error was caused by the certificate validation issue so I tried to apply the solution mentioned in my earlier post. No luck this time.

After some research the problem was solved by setting:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3

Quick note, why this was helpful.
It's all related to protocols - SSL3 and TLS - used to establish a secure communication between two endpoints.

  • SSL3 is a security protocol released in 1996 by Netscape Communications and it is a superset of SSL2. 
  • TLS (TLS 1.0 or sometimes known as SSL 3.1) was introduced by IETF in 1999 (RFC 2246) and is a superset of SSL3 although not 100% backward compatible.

More information about the protocols mentioned above could be found here.

.NET applications use TLS for transport security by default. If you look at the static constructor of the ServicePointManager class you would notice the following line:

s_SecurityProtocolType = SecurityProtocolType.Tls | SecurityProtocolType.Ssl3;

My wild guess would be that initially TLS protocol is used to establish a secure connection with the server. In case when the server does not support TLS, the negotiation continues using SSL3 protocol. The problem is that some servers not supporting TLS terminate connection immediately. This is why in such cases SSL3 communication needs to be forced.

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;
}