开发者

How do I set the 'ServerCertificateValidationCallback' property back to its default behavior?

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!


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

ServicePointManager.ServerCertificateValidationCallback = Nothing


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


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


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜