How to properly validate X.509 certificates in C# with .NET Core 3.1 & .NET 5+

I have been recently working on a project that required issuing certificates from a self-signed root CA, and while trying to verify those certificates from a C# project, I discovered that the AllowUnknownCertificateAuthority X509VerificationFlag was not behaving as expected; the docs (at the time) shows that this flag would ignore only UntrustedRoot when in reality it was also ignoring the PartialChain status!

I detail the consequences in dotnet/runtime#49615; namely X509Chain.Build() -- which is intended to return a simple bool representing if the certificate was verified as trusted or not -- was returning true even if the certificate under validation was not issued by any of the trusted root CAs or those in ExtraStore (i.e., it considers a new chain consisting only the certificate under validation and determines that to be a partial chain, which is then ignored).

The X509VerificationFlag docs have since been corrected to include PartialChain in the behavior of AllowUnknownCertificateAuthority flag, but contributions of further examples to the docs were rejected and I do not believe that the docs provide sufficient clarity around the issues with the .NET 3.x and prior APIs:

Unless developers are using .NET 5 and the X509Chain.CustomTrustStore property, the return value of X509Chain.Build() should not be trusted on its own; proper certificate verification requires developers manually and separately perform manual verification of correct chain termination (i.e. checking the last item in the chain is indeed the signing root CA we expect).

Thus, I have created a new GitHub repository stewartadam/dotnet-x509-certificate-verification that describes these issues in detail and provides code samples for securely validating X.509 certificates on both .NET Core and .NET 5 including with self-signed root CAs.