Stephen
Stephen

Reputation: 41

how to detect certificates with incomplete certificate chains using C#? C# dotnet core says they're valid instead of invalid

I'm attempting to write some code to validate if a website's certificate is valid (Ex. incomplete-chain.badssl.com). This issue I'm running into is that it appears windows (or the dotnet framework) is being overly helpfully during certificate validation, even going so far as to fix the chain. By the time the chain gets to my validate certificate callback functions, the chain is different than the host server and is valid. Does anyone know of a way to bypass what appears to be the cryptoapi's chain fixing feature and just use certificate "as is" to validate the chain?

I've tried with both the httpclient's and sslstream's validate certificate callbacks. Both say the chain is valid instead of invalid.

curl requests, for example, fail how I want my requests to fail if the cert chain is not valid.

 curl get https://incomplete-chain.badssl.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:--  0:00:02 --:--:--     0
curl: (6) Could not resolve host: get
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

openssl also correctly shows these incomplete-chains as invalid

$ openssl s_client -showcerts -connect incomplete-chain.badssl.com:443
CONNECTED(00000188)
---
Certificate chain
 0 s:C = US, ST = California, L = Walnut Creek, O = Lucas Garron Torres, CN = *.badssl.com
   i:C = US, O = DigiCert Inc, CN = DigiCert SHA2 Secure Server CA
-----BEGIN CERTIFICATE-----
the certificate 
-----END CERTIFICATE-----
---
Server certificate
subject=C = US, ST = California, L = Walnut Creek, O = Lucas Garron Torres, CN = *.badssl.com

issuer=C = US, O = DigiCert Inc, CN = DigiCert SHA2 Secure Server CA

---
No client certificate CA names sent
Peer signing digest: SHA512
Peer signature type: RSA
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 2414 bytes and written 455 bytes
Verification error: unable to verify the first certificate
---

EDIT: Here's an attempt using WebRequest (https://dotnetfiddle.net/2aybuI)

using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

public class Program
{
    public static void Main(string[] args)
    {
        var request = (HttpWebRequest)WebRequest.Create("https://incomplete-chain.badssl.com");
        request.AllowAutoRedirect = false;
        request.ServerCertificateValidationCallback = ServerCertificateValidationCallback;
        var response = (HttpWebResponse)request.GetResponse();
        response.Close();
    }

    private static bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors != SslPolicyErrors.None)
        {
            return false;
        }

        var newChain = new X509Chain();
        newChain.ChainPolicy.DisableCertificateDownloads = true;

        // Skip the leaf cert and stop short of the root cert.      
        X509ChainElementCollection chainElements = chain.ChainElements;
        for (int i = 1; i < chainElements.Count - 1; i++)
        {
            newChain.ChainPolicy.ExtraStore.Add(chainElements[i].Certificate);
        }

        var result = newChain.Build(chainElements[0].Certificate);

        // This is True and I want it to be False
        Console.WriteLine($"This is {result} and I want it to be False");
        return result;
    }
}

Here are two old questions that seem to have a similar problem, but have no solutions.

  1. SSL Certificate with Incomplete-Chain, passes validation in .NET Core 2.2

  2. X509Chain.Build() method returns true for Invalid certificate

Upvotes: 2

Views: 1288

Answers (1)

bartonjs
bartonjs

Reputation: 33108

If you're using .NET 5+, you can set chain.ChainPolicy.DisableCertificateDownloads = true to prevent it from using AIA records to rebuild the chain. Note that this won't bypass resolving things from local caches, so if an intermediate is already cached you'll still end up with a fully built chain.

There's not a way to get that set on chains built by SslStream to validate remote certificates, and SslStream building the chain may cause the underlying system to cache the intermediate (since it had to download it, and it chained to a trusted root, so it's cache-worthy).

Upvotes: 1

Related Questions