I’ve taken for granted how often I need to call a web API from my C# applications. If you’ve ever had to make a web request in your .NET application, then I’m sure you’ve seen the following frustrating exception message as many times as I have.

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.– The depths of the .NET Framework

This error message is so blunt, yet so confusing to most developers. So what’s the cause and how do you fix it?

Normally, the exception is caused by an un-validated certificate at the target server. By default, .NET web requests need to verify the target of their request and the way developers can validate an unknown certificate is by using the ServicePointManager.

If you need something that “works”, then you might use the following code.

ServicePointManager.ServerCertificateValidationCallback +=
    (sender, certificate, chain, errors) => {
        return true;
    };

I do not recommend leaving this hack in your codebase. The above code is a security risk, as any certificate, even malicious ones will be accepted. If you want a better solution read on.

What Is The ServicePointManager

The Microsoft docs succinctly describes the purpose of the ServicePointManager class.

When an application requests a connection to an Internet resource Uniform Resource Identifier (URI) through the ServicePointManager object, the ServicePointManager returns a ServicePoint object that contains connection information for the host and scheme identified by the URI. Microsoft

The class is essentially a connection manager for outgoing web requests. To validate these connections, you will need to subscribe to the ServerCertificateValidationCallback event, which fires on every certificate validation.

ServicePointManager For Development

If you’re writing a console application or throw-away piece of code, you can use this naive implementation of a validator.

ServicePointManager.ServerCertificateValidationCallback +=
    (sender, certificate, chain, errors) =>
    {
        // local dev, just approve all certs
        if (development) return true;
        return errors == SslPolicyErrors.None ;
    };

The local development implementation makes sure there are at least no errors with the SSL policy.

ServicePointManager For Production

If you trust and know the locations are calling, you can validate against a dictionary of certificate hashes. These certificates can either be preloaded on to the host machine or read from disk. I prefer this second impelmentation of ServerCertificateValidationCallback in most cases.

ServicePointManager.ServerCertificateValidationCallback +=
    (sender, certificate, chain, errors) =>
    {
        // local dev, just approve all certs
        if (development) return true;
        return errors == SslPolicyErrors.None
            && validCerts.Contains(certificate.GetCertHashString());
    };

The validCerts variable is a dictionary that contains the hash strings of the X509 certificates you trust. You can load these up from a configuration file or a database. You can get the hash strings from loading the certs you trust into a .NET console app and spitting them out.

var hash = X509Certificate
    .CreateFromCertFile("mycert.cer")
    .GetCertHashString();

Utilizing trusted certificates ensures you stop any sensitive requests from going to an unknown server. When starting a development project, you can start by accepting all certificates during local development to reduce friction, but remember to harden your security before code is deployed into production.