How to verify an X509 certificate without importing a root certificate?

My program contains 2 root certificates that I know and trust. I have to check the certificates of trust centers and the "user" certificates issued by trust centers, they all come from these two root certificates.

I use the X509Chain class for verification, but this only works if the root certificate is in the Windows certificate store.

I'm looking for a way to verify certificates without importing theeses root certificates - somehow tell the X509Chain class that I trust these root certificates, and it should only check the certificates in the chain and nothing more.

Actual code:

X509Chain chain = new X509Chain(); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.ExtraStore.Add(root); // i do trust this chain.ChainPolicy.ExtraStore.Add(trust); chain.Build(cert); 

Edit: this is a .NET 2.0 Winforms application.

+6
source share
4 answers

I found a solution for this not to rely on the result of the build method, but instead to check the ChainStatus property. (Not tested on .NET 2.0, but this is the only solution I found for this common problem)

 X509Chain chain = new X509Chain(); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.ExtraStore.Add(root); chain.Build(cert); if (chain.ChainStatus.Length == 1 && chain.ChainStatus.First().Status == X509ChainStatusFlags.UntrustedRoot) { // chain is valid, thus cert signed by root certificate // and we expect that root is untrusted which the status flag tells us } else { // not valid for one or more reasons } 

It was also found that the installation

 ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; 

will allow the build method to return true, even if you do not add certificates to ExtraStore, which completely defeats the purpose of verification. I do not recommend using this flag for any reason.

+3
source

By getting this will write a custom check.

If you are in the WCF context, this is done by subclassing System.IdentityModel.Selectors.X509CertificateValidator and specifying a user check on the serviceBehavior object in web.config:

 <serviceBehaviors> <behavior name="IdentityService"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> <serviceCredentials> <clientCertificate> <authentication customCertificateValidatorType="SSOUtilities.MatchInstalledCertificateCertificateValidator, SSOUtilities" certificateValidationMode="Custom" /> </clientCertificate> <serviceCertificate findValue="CN=SSO ApplicationManagement" storeLocation="LocalMachine" storeName="My" /> </serviceCredentials> </behavior> 

But if you just look at the method of accepting SSL certificates from another host, you can change the system.net settings in the web.config file:

The following is an X509CertificateValidator example that checks for a client certificate in the LocalMachine / Personal repository. (This is not what you need, but may be useful as an example.

 using System.Collections.Generic; using System.Linq; using System.Security; using System.Security.Cryptography.X509Certificates; /// <summary> /// This class can be injected into the WCF validation /// mechanism to create more strict certificate validation /// based on the certificates common name. /// </summary> public class MatchInstalledCertificateCertificateValidator : System.IdentityModel.Selectors.X509CertificateValidator { /// <summary> /// Initializes a new instance of the MatchInstalledCertificateCertificateValidator class. /// </summary> public MatchInstalledCertificateCertificateValidator() { } /// <summary> /// Validates the certificate. Throws SecurityException if the certificate /// does not validate correctly. /// </summary> /// <param name="certificateToValidate">Certificate to validate</param> public override void Validate(X509Certificate2 certificateToValidate) { var log = SSOLog.GetLogger(this.GetType()); log.Debug("Validating certificate: " + certificateToValidate.SubjectName.Name + " (" + certificateToValidate.Thumbprint + ")"); if (!GetAcceptedCertificates().Where(cert => certificateToValidate.Thumbprint == cert.Thumbprint).Any()) { log.Info(string.Format("Rejecting certificate: {0}, ({1})", certificateToValidate.SubjectName.Name, certificateToValidate.Thumbprint)); throw new SecurityException("The certificate " + certificateToValidate + " with thumprint " + certificateToValidate.Thumbprint + " was not found in the certificate store"); } log.Info(string.Format("Accepting certificate: {0}, ({1})", certificateToValidate.SubjectName.Name, certificateToValidate.Thumbprint)); } /// <summary> /// Returns all accepted certificates which is the certificates present in /// the LocalMachine/Personal store. /// </summary> /// <returns>A set of certificates considered valid by the validator</returns> private IEnumerable<X509Certificate2> GetAcceptedCertificates() { X509Store k = new X509Store(StoreName.My, StoreLocation.LocalMachine); try { k.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); foreach (var cert in k.Certificates) { yield return cert; } } finally { k.Close(); } } } 
+1
source

If you know which certificates can be root and intermediate certificates for certificate verification, you can load the public keys of the root and intermediate certificates in the ChainPolicy.ExtraStore collection of the ChainPolicy.ExtraStore object.

My task was also to write a Windows Forms application to install the certificate only if it was released depending on the famous "National Root Certificate" of my government. There is also a limited number of CAs that are allowed to issue certificates for authenticating connections to national web services, so I had a limited set of certificates that might be in the chain and might not be on the target machine. I collected all of the CA public keys and government root certificates in the "cert" subdirectory of the application: chain certificates

In Visual Studio, I added a directory certificate to the solution and marked all the files in this directory as an embedded resource. This allowed me to list the collection of "trusted" certificates in my C # library code to create a chain for certificate verification, even if the issuer certificate is not installed. For this purpose, I created a wrapper class for X509Chain:

 private class X509TestChain : X509Chain, IDisposable { public X509TestChain(X509Certificate2 oCert) : base(false) { try { ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; if (!Build(oCert) || (ChainElements.Count <= 1)) { Trace.WriteLine("X509Chain.Build failed with installed certificates."); Assembly asmExe = System.Reflection.Assembly.GetEntryAssembly(); if (asmExe != null) { string[] asResources = asmExe.GetManifestResourceNames(); foreach (string sResource in asResources) { if (sResource.IndexOf(".cert.") >= 0) { try { using (Stream str = asmExe.GetManifestResourceStream(sResource)) using (BinaryReader br = new BinaryReader(str)) { byte[] abResCert = new byte[str.Length]; br.Read(abResCert, 0, abResCert.Length); X509Certificate2 oResCert = new X509Certificate2(abResCert); Trace.WriteLine("Adding extra certificate: " + oResCert.Subject); ChainPolicy.ExtraStore.Add(oResCert); } } catch (Exception ex) { Trace.Write(ex); } } } } if (Build(oCert) && (ChainElements.Count > 1)) Trace.WriteLine("X509Chain.Build succeeded with extra certificates."); else Trace.WriteLine("X509Chain.Build still fails with extra certificates."); } } catch (Exception ex) { Trace.Write(ex); } } public void Dispose() { try { Trace.WriteLine(string.Format("Dispose: remove {0} extra certificates.", ChainPolicy.ExtraStore.Count)); ChainPolicy.ExtraStore.Clear(); } catch (Exception ex) { Trace.Write(ex); } } } 

In the calling function, I could now successfully verify that an unknown certificate was received from the national root certificate:

  bool bChainOK = false; using (X509TestChain oChain = new X509TestChain(oCert)) { if ((oChain.ChainElements.Count > 0) && IsPKIOverheidRootCert(oChain.ChainElements[oChain.ChainElements.Count - 1].Certificate)) bChainOK = true; if (!bChainOK) { TraceChain(oChain); sMessage = "Root certificate not present or not PKI Overheid (Staat der Nederlanden)"; return false; } } return true; 

To complete the image: check the root certificate (which is usually installed because it is included in Windows Update, but theoretically may also be missing), I compare the friendly name and fingerprint with the published values:

 private static bool IsPKIOverheidRootCert(X509Certificate2 oCert) { if (oCert != null) { string sFriendlyName = oCert.FriendlyName; if ((sFriendlyName.IndexOf("Staat der Nederlanden") >= 0) && (sFriendlyName.IndexOf(" Root CA") >= 0)) { switch (oCert.Thumbprint) { case "101DFA3FD50BCBBB9BB5600C1955A41AF4733A04": // Staat der Nederlanden Root CA - G1 case "59AF82799186C7B47507CBCF035746EB04DDB716": // Staat der Nederlanden Root CA - G2 case "76E27EC14FDB82C1C0A675B505BE3D29B4EDDBBB": // Staat der Nederlanden EV Root CA return true; } } } return false; } 

I'm not sure this check is completely safe, but in my case the Windows Forms application operator is sure that you have access to a valid certificate that will be installed. The purpose of the software is simply to filter the list of certificates to help it install only the correct certificate in the computer's computer storage (the software also installs the public keys of the intermediate and root certificate to ensure that the behavior of the runtime client is a web service).

+1
source

I simply expanded @Tristan's code to verify that the root certificate is one of the certificates added to ExtraStore.

 X509Chain chain = new X509Chain(); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.ExtraStore.Add(root); chain.Build(cert); if (chain.ChainStatus.Length == 1 && chain.ChainStatus.First().Status == X509ChainStatusFlags.UntrustedRoot && chain.ChainPolicy.ExtraStore.Contains(chain.ChainElements[chain.ChainElements.Count - 1].Certificate)) { // chain is valid, thus cert signed by root certificate // and we expect that root is untrusted which the status flag tells us // but we check that it is a known certificate } else { // not valid for one or more reasons } 
0
source

Source: https://habr.com/ru/post/888821/


All Articles