18

I use the following line of code within a single method to explicitly check and trust an SSL cert from the following host: MyTrustedCompany.com:

ServicePointManager.ServerCertificateValidationCallback = Function(obj As [Object], certificate As X509Certificate, chain As X509Chain, errors As SslPolicyErrors) (certificate.Subject.Contains("CN=MyTrustedCompany.com"))

No problem with the code -> works perfectly 100%.

The problem is, it is too far reaching. I thought its scope would only be within the method I decalred it, but apparently it is a Shared property on the 'ServicePointManager' object, and must then persist for the entire application, which I do not want.

The problem is later I am calling web services of mine, etc and getting the "Could not establish a trust relationship..." exception. This is because in the line of code above I check for the host name of an SSL cert specefic to that method. I quickly tested Returning 'True' from the callback so all certs would be trusted instead of checking for a specefic name (i.e. MyTrustedCompany) and subsiquent requests worked. This is how I know this callback assignment reaches father than that single method. Sure I could extend the callback to include all other certitificate names, but what I would rather do is set the 'ServerCertificateValidationCallback' back to its default behavior. Like the pseudo code below:

ServicePointManager.ServerCertificateValidationCallback = Nothing  'Default checking behavior

How do I remove the custom validation and set it back to its default behavior? Thanks!

atconway
  • 20,624
  • 30
  • 159
  • 229
  • You could probably set it equal to a function that does what the default behavior is: When custom validation is not used, the certificate name is compared with host name used to create the request. For example, if Create(String) was passed a parameter of "https://www.contoso.com/default.hmtl", the default behavior is for the client to check the certificate against www.contoso.com. http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx – Prescott Nov 11 '10 at 22:25
  • A similar question has an answer which looks good https://stackoverflow.com/a/44803221/57428 – sharptooth Aug 26 '20 at 12:18

5 Answers5

13

This actually appears to work (as simple as it is) and makes the object behave in its default manner.

ServicePointManager.ServerCertificateValidationCallback = Nothing
atconway
  • 20,624
  • 30
  • 159
  • 229
  • 9
    Have you figured out if there is any way to make this thread-safe? Since this thing is static, I would assume, every thread that uses web services would call your delegate while it's set. – galets Sep 13 '11 at 22:36
  • (Realizing this is really old...) Are you sure that works? Setting to null in C# [reportedly sends everything through](http://stackoverflow.com/a/8722292/1028230). – ruffin Jan 17 '15 at 21:21
  • Yes it did work setting the behavior back to its default in that it would then begin to discriminate against SSL certificates and _not_ allow everything through. It was the assigned callback method that had the functionality to `return true` which was what allowed everything through. – atconway Jan 19 '15 at 15:30
4

A key point to solving your problem is the fact that the sender parameter to the RemoteCertificateValidationCallback is the WebRequest. You can check the sender against your webrequest so you only do checking for your webrequest. Here is my (relatively untested) solution:

// Main Code

request = (FtpWebRequest)FtpWebRequest.Create("ftp://example.com");

using(var validator = new WebRequestCertificateValidator(request))
{
    // etc...
}

// WebRequestCertificateValidator Class

public sealed class WebRequestCertificateValidator : IDisposable
{
    private bool disposed;

    private WebRequest request;

    private RemoteCertificateValidationCallback callback;

    /// <summary>
    /// Creates a certificate validator that allows all certificates for the supplied web request.
    /// </summary>
    /// <param name="request">The WebRequest to validate for.</param>
    public WebRequestCertificateValidator(WebRequest request) : this(request, null)
    {
        //
    }

    /// <summary>
    /// Creates a certificate validator that only allows certificates for the supplied web request of the callback returns true.
    /// </summary>
    /// <param name="request">The WebRequest to validate for.</param>
    /// <param name="callback">The delegate that will be called to validate certificates for the WebRequest.</param>
    public WebRequestCertificateValidator(WebRequest request, RemoteCertificateValidationCallback callback)
    {
        this.disposed = false;

        this.request = request;

        this.callback = callback;

        ServicePointManager.ServerCertificateValidationCallback += this.InternalCallback;
    }

    private bool InternalCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        WebRequest request = sender as WebRequest;

        if(request != null)
        {
            if(request == this.request)
            {
                if(this.callback != null)
                {
                    return this.callback(sender, certificate, chain, sslPolicyErrors);
                }
            }
        }

        return true;
    }

    public void Dispose()
    {
        if(!this.disposed)
        {
            ServicePointManager.ServerCertificateValidationCallback -= this.InternalCallback;

            this.callback = null;

            this.request = null;

            this.disposed = true;
        }
    }
}
Tom Winter
  • 1,813
  • 3
  • 18
  • 23
4

You'll want to only return true for certain URLs, but otherwise, this does what you want, leaving the possibility of using multiple delegates. The logic for each callback could include a check via reflection so that the callback only returns true when called from certain components, thus creating a sort of tunnel between certain URLs and certain applications.

One way to use this code: 1. Define the mIgnoreBadCertificates delegate early in your object's life 2. Set a property containing the 'beSecure' code true 3. Send the Http Request. 4. Set the property false. This is very important, and should be implemented in a way that guarantees it gets called. The IDisposable pattern is one option.

 private System.Net.Security.RemoteCertificateValidationCallback mIgnoreBadCertificates = new
    System.Net.Security.RemoteCertificateValidationCallback(
      delegate { return true; });

if (beSecure)
    {   //require secure communications
        System.Net.ServicePointManager.ServerCertificateValidationCallback -= mIgnoreBadCertificates;
        Iwds.EventLogger.LogVeryFrequentEvent("Requiring Good Certificates from Remote Sites");
    }
    else
    {   /// Allow connections to SSL sites that have unsafe certificates.
        System.Net.ServicePointManager.ServerCertificateValidationCallback += mIgnoreBadCertificates;
        Iwds.EventLogger.LogVeryFrequentEvent("Ignoring Bad Certificates from Remote Sites");
    }
Tom
  • 41
  • 1
1
public class OAuthRequestHandler : WebRequestHandler
{
    public OAuthRequestHandler() : base()
    {
        base.ServerCertificateValidationCallback  += this.InternalCallback;
    }

    private bool InternalCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        return certificate.Subject.Contains("CN=MyTrustedCompany.com");
    }
}

and in my program.cs commandline test:

HttpClient client = new HttpClient(new OAuthRequestHandler());
responseString = await client.GetStringAsync("https://localhost:2345");

It is possible to do more with the certificate and sender parameters but the OP asked for the above.

OzBob
  • 4,227
  • 1
  • 39
  • 48
  • 2
    To be precise the above solution introduces a serious security issue, the condition should be: return (sslPolicyErrors == SslPolicyErrors.None) && certificate.Subject.Contains("CN=MyTrustedCompany.com"); – too Mar 18 '15 at 12:46
0

The only reliable way to make sure that the custom callback is reliably reverted and the whole thing is also thread safe it to put the code which needs a subverted callback into a separate process. In many cases this is quite easy to do - you could for example run the separate process code using Process class and passing it a command line. gRPC or WCF are other communication options.

This way only the separate process sets the custom callback and the main process is unaffected.

sharptooth
  • 167,383
  • 100
  • 513
  • 979