C# / .NET - How to allow a “custom” Root-CA for HTTPS in my application (only)?

后端 未结 2 857
囚心锁ツ
囚心锁ツ 2021-02-04 04:08

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

2条回答
  •  野趣味
    野趣味 (楼主)
    2021-02-04 05:05

    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.

提交回复
热议问题