runtime: RemoteCertificateValidationCallback can not detect some incomplete certificate chains

Description

It appears that RemoteCertificateValidationCallback (tested with SslStream and HttpWebRequest ) incorrectly validates some certificates with chain issues (ex. https://incomplete-chain.badssl.com). The code I’m attempting to write validates certificates, similar to what this tool is doing https://www.sslshopper.com/ssl-checker.html.

There does not appear to be a way to detect these issues, which is why this feels like a bug to me.

Here is my attempt at overriding the default behavior, but by the time my code it hit, all 3 certificates look like they exist. (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;
	}
}

Configuration

Here is a sample app using .NET 5 (my code base is .NET core 3.1 with the same issue)

Here’s a .NET Fiddle demonstrating the code above. https://dotnetfiddle.net/2aybuI

Regression?

Tested on several versions of .NET and this bug has been around for a long time.

Other information

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

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 22 (13 by maintainers)

Most upvoted comments

The trusted root should not be sent. You either have root in your trust or you won’t be able to validate anyway. Sending root CA does not really help and it is only waste. It should be valid to send it but the sample code I posted simply ignores root.

For TLS 1.2

   certificate_list
      This is a sequence (chain) of certificates.  The sender's
      certificate MUST come first in the list.  Each following
      certificate MUST directly certify the one preceding it.  Because
      certificate validation requires that root keys be distributed
      independently, the self-signed certificate that specifies the root
      certificate authority MAY be omitted from the chain, under the
      assumption that the remote end must already possess it in order to
      validate it in any case.

TLS 1.3 only adds note that order should not mater.

As far as the link: It clearly states The certificate chain sent by this site is missing an intermediate certificate. This will cause a certificate error unless the browser already has the intermediate certificate in a cache or implements [AIA fetching](https://tools.ietf.org/html/rfc3280#section-4.2.2.1).

I agree that adding control for AIA would be useful. That is already tracked by #59979.

There was never attempt to order the certificates anyhow. There were some platform differences (like extra leaf cert) I tried to fix in 7.0. Tls 1.3 explicitly states that receiver should make no assumption about the order

 Note: Prior to TLS 1.3, "certificate_list" ordering required each
   certificate to certify the one immediately preceding it; however,
   some implementations allowed some flexibility.  Servers sometimes
   send both a current and deprecated intermediate for transitional
   purposes, and others are simply configured incorrectly, but these
   cases can nonetheless be validated properly.  For maximum
   compatibility, all implementations SHOULD be prepared to handle
   potentially extraneous certificates and arbitrary orderings from any
   TLS version, with the exception of the end-entity certificate which
   MUST be first.

@stephenweaver

Possible work around:

  1. implement the SSL and TLS handshake, and check the cert ref: [TestSSLServer ](https://github.com/pornin/TestSSLServer )
  2. windows.security.cryptography.certificates.chainbuildingparameters.networkretrievalenabled ref: https://docs.microsoft.com/en-us/uwp/api/windows.security.cryptography.certificates.chainbuildingparameters.networkretrievalenabled?view=winrt-10240#Windows_Security_Cryptography_Certificates_ChainBuildingParameters_NetworkRetrievalEnabled

I’m working on the first one. Just failed with the ChainPolicy.DisableCertificateDownloads today. May i should write the tool with other language …

the third work around, which is proved works fine: use js running in NodeJs to detect the incomplete-chain.badssl.com use Jering.Javascript.NodeJS to interop with NodeJs done

https://github.com/shamork/detect-incomplete-chain