Okay, here is what I need to do:
My application, written in C# (.NET Framework 4.5), needs to communicate with our server via HTTPS. Our server uses a TLS/SSL certificat
The RemoteCertificateValidationCallback
delegate is the right way to your solution. However, I would use a different behavior in the delegate, than suggested by Olivier. That's why: too many irrelevant checks are performed and relevant are not.
So, look at the issue in details:
At first, we shall consider the scenario when your service uses legitimate certificate purchased from commercial CA (this may not be the case right now, but may be in some future). This means that if sslPolicyErrors
parameter has None
flag presented, immediately return True
, the certificate is valid and there are no obvious reasons to reject it. This step is necessary only if the following your statement is NOT strict:
Only our Root-CA needs to be accepted as a "trusted" root for my application.
otherwise, ignore first step.
Let's assume, the service still uses certificate from private and untrusted CA. In this case we have to handle errors which are not related to certificate chain and are specific only to SSL session. Thus, when the RemoteCertificateValidationCallback
delegate is called, we shall ensure that RemoteCertificateNameMismatch
and RemoteCertificateNotAvailable
flags are not presented in the sslPolicyErrors
parameter. If any of them presented, we shall reject connection without additional checks.
Let's assume that none of these flags presented. At this point we correctly handled SSL-specific errors and only certificate chain may have issues.
If we reach this far, we can claim that sslPolicyErrors
parameter contains RemoteCertificateChainErrors
flag. This can mean everything and we have to make additional checks. Your root CA certificate is a constant. This means that we can examine root certificate in the chain
parameter and compare it with our constant (Root CA certificate's thumbprint, for example). If comparison fails, we immediately reject the certificate, because it is not your's and there are no obvious reasons to trust certificate issued by an unknown CA and which may have other chain issues.
If comparison succeeds, then we reached the case we have to handle carefully and properly. We have to execute another instance of certificate chaining engine and instruct it to collect any chain issues, except UntrustedRoot
error only. This means that if SSL certificate has other issues (RevocationOffline, validity, policy errors for example) we will know about that and will reject this certificate.
The code below is a programmatical implementation of many words above:
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace MyNamespace {
class MyClass {
Boolean ServerCertificateValidationCallback(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
String rootCAThumbprint = ""; // write your code to get your CA's thumbprint
// remove this line if commercial CAs are not allowed to issue certificate for your service.
if ((sslPolicyErrors & (SslPolicyErrors.None)) > 0) { return true; }
if (
(sslPolicyErrors & (SslPolicyErrors.RemoteCertificateNameMismatch)) > 0 ||
(sslPolicyErrors & (SslPolicyErrors.RemoteCertificateNotAvailable)) > 0
) { return false; }
// get last chain element that should contain root CA certificate
// but this may not be the case in partial chains
X509Certificate2 projectedRootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
if (projectedRootCert.Thumbprint != rootCAThumbprint) {
return false;
}
// execute certificate chaining engine and ignore only "UntrustedRoot" error
X509Chain customChain = new X509Chain {
ChainPolicy = {
VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority
}
};
Boolean retValue = customChain.Build(chain.ChainElements[0].Certificate);
// RELEASE unmanaged resources behind X509Chain class.
customChain.Reset();
return retValue;
}
}
}
This method (named delegate) can be attached to ServicePointManager.ServerCertificateValidationCallback
. The code might be compacted (combine multiple IF's in one IF statement, for example), I used verbose version to reflect textual logic.