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

后端 未结 2 862
囚心锁ツ
囚心锁ツ 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 04:56

    You can use that callback and don't have to reinvent the wheel.

    If you take a closer look, the callback has multiple parameters:

    public delegate bool RemoteCertificateValidationCallback(
        object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors
    )
    

    The parameters are the result of the check through the .Net implementation. You can e.g. check if the only problem is the missing root CA by using the following code:

    // Check if the only error of the chain is the missing root CA,
    // otherwise reject the given certificate.
    if (chain.ChainStatus.Any(statusFlags => statusFlags.Status != X509ChainStatusFlags.UntrustedRoot))
        return false;
    

    It will iterate through the whole chain and checks all states. If any is anything else then untrusted root (beware: the enum has a flags attribute) we fail. But if the only bad thing is the not trusted root, you can take it.

    But another problem you get now, is that you know that this certificate has an untrusted root, but you don't know if that is really your certificate (or any other).

    To ensure this, you have to read the public part of your server certificate that you have stored with your application and compare it to the given chain:

    // Read CA certificate from file.
    var now = DateTime.UtcNow;
    var certificateAuthority = new X509Certificate(_ServerCertificateLocation);
    var caEffectiveDate = DateTime.Parse(certificateAuthority.GetEffectiveDateString());
    var caExpirationDate = DateTime.Parse(certificateAuthority.GetExpirationDateString());
    
    // Check if CA certificate is valid.
    if (now <= caEffectiveDate
        || now > caExpirationDate)
        return false;
    
    // Check if CA certificate is available in the chain.
    return chain.ChainElements.Cast()
                              .Select(element => element.Certificate)
                              .Where(chainCertificate => chainCertificate.Subject == certificateAuthority.Subject)
                              .Where(chainCertificate => chainCertificate.GetRawCertData().SequenceEqual(certificateAuthority.GetRawCertData()))
                              .Any();
    

    Maybe it would be wise to add a fast exit at the beginning of the function, if the server delivers a certificate that is signed by an installed root CA (which is maybe not yours!):

    if (sslPolicyErrors == SslPolicyErrors.None
        && chain.ChainStatus.All(statusFlags => statusFlags.Status == X509ChainStatusFlags.NoError))
        return true;
    

    Also if desired (depending on your needs) you can allow or disallow to use a certificate, where the server name and certificate name doesn't match. This happens, e.g. if the certificate was made for localhost and you access the server from a different machine. Or within a intranet you only use http://myserver instead of http://myserver.domain.com, but the certificate was made on the full qualified name (or vice versa):

    if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
        return false;
    

    And that's it. You still rely on the default implementation and only additionally check your parts.

提交回复
热议问题