How to verify validity of certificates with .NET

Hi all,

 

The other day a customer of mine was trying to verify the validity of a certificate with a .NET code like the following:

 Dim cert As X509Certificate2 = New X509Certificate2(filename) 
 
 Dim chain As New X509Chain()
 chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain
 chain.ChainPolicy.RevocationMode = X509RevocationMode.Online
 
 chain.ChainPolicy.VerificationFlags = _
 X509VerificationFlags.IgnoreCtlSignerRevocationUnknown Or _
 X509VerificationFlags.IgnoreRootRevocationUnknown Or _
 X509VerificationFlags.IgnoreEndRevocationUnknown Or _
 X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown Or _
 X509VerificationFlags.IgnoreCtlNotTimeValid
 
 chain.Build(cert)
 Dim bVerif As Boolean = cert.Verify() 

 

He was wondering what was going to the value of bVerif, and if the following collection was going to be filled in case of bVerif being false:

 Dim status As X509ChainStatusFlags
 Dim info As String
 Dim chainElement As X509ChainElement
 For Each chainElement In chain.ChainElements
 Dim chainStatus As X509ChainStatus
 For Each chainStatus In chainElement.ChainElementStatus
 status = chainStatus.Status
 info = chainStatus.StatusInformation
 MsgBox(info)
 Next
 Next

 

First of all, we don’t need to call “cert.Verify()” in the code above at all. If we want basic validation, we use Verify method, but if we want to control the validation with specific flags the way the code above is doing it, we have to use X509Chain and the Build method.

X509Certificate2.Verify Method
"
Performs a X.509 chain validation using basic validation policy.

Remarks
--------------------------------------------------------------------------------
This method builds a simple chain for the certificate and applies the base policy to that chain. If you need more information about a failure, validate the certificate directly using the X509Chain object.
"

Additionally, note that the Build method already returns if the certificate is valid or not with the flags we passed.

X509Chain.Build Method
"
Return Value
Type: System.Boolean
true if the X.509 certificate is valid; otherwise, false.
"

So something like this would do: "Dim bVerif As Boolean = chain.Build(cert) "

After we call Build method, X509ChainStatus should be filled with the errors if any. This sample from an Spanish blog shows how to get that info:

Validar certificados
"

 objChain.Build(objCert); 
 if (objChain.ChainStatus.Length != 0) 
 {
 foreach (X509ChainStatus objChainStatus in objChain.ChainStatus) 
 Debug.Print(objChainStatus.Status.ToString() + " - " + objChainStatus.StatusInformation); 
 } 

"

 

This is basically what happens when we call Build:

1) ChainStatus gets populated with all errors we find in the chain, regardless of the flags we pass to ChainPolicy.VerificationFlags. So if there is any error, ChainStatus.Length will always be > 0.
2) Build returns TRUE if ChainStatus.Length is 0 (no errors), or if ChainStatus.Length > 0 but we decided to ignore all those errors with some flags in ChainPolicy.VerificationFlags.

So basically, we should always check the value that Build returns, TRUE or FALSE, and if it is FALSE, then check the ChainStatus for the specific errors.

 

For example, let's take a certificate that is not time valid. With the following code, the call to Verify will return false, the first call to Build will return true (as there are errors in ChainStatus but we ignored them with VerificationFlags), and the second call to Build will return false (as there are errors and we didn't ignore them thx to X509VerificationFlags.NoFlag):

 Imports System.IO
 Imports System.Security.Cryptography.X509Certificates
 
 Module Module1
 
 Sub Main()
 Dim s() As String = System.Environment.GetCommandLineArgs()
 If s.Length < 2 Then
 MsgBox("Usage : VbConsoleApp fileEncrypted, where fileEncrypted is the file that contains the dates encrypted" & vbCrLf)
 Exit Sub
 End If
 
 Dim cert As X509Certificate2 = New X509Certificate2(s(1))
 Console.WriteLine(cert.SubjectName.Name)
 
 Dim bRet As Boolean = cert.Verify()
 Console.WriteLine("Verify returns " + bRet.ToString())
 Console.WriteLine("****************************************************")
 
 Dim ch1 As New X509Chain()
 ch1.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain
 ch1.ChainPolicy.RevocationMode = X509RevocationMode.Online
 ch1.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreCtlSignerRevocationUnknown Or _
 X509VerificationFlags.IgnoreRootRevocationUnknown Or _
 X509VerificationFlags.IgnoreEndRevocationUnknown Or _
 X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown Or _
 X509VerificationFlags.IgnoreCtlNotTimeValid Or _
 X509VerificationFlags.AllowUnknownCertificateAuthority
 
 Dim bRet1 As Boolean = ch1.Build(cert)
 Console.WriteLine("Build with flags returns " + bRet1.ToString())
 
 For Each status1 As X509ChainStatus In ch1.ChainStatus
 Console.WriteLine(status1.Status.ToString() + " --> " + status1.StatusInformation)
 Next
 Console.WriteLine("****************************************************")
 
 Dim ch2 As New X509Chain()
 ch2.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain
 ch2.ChainPolicy.RevocationMode = X509RevocationMode.Online
 ch2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag
 
 Dim bRet2 As Boolean = ch2.Build(cert)
 Console.WriteLine("Build without flags returns " + bRet2.ToString())
 For Each status2 As X509ChainStatus In ch2.ChainStatus
 Console.WriteLine(status2.Status.ToString() + " --> " + status2.StatusInformation)
 Next
 Console.WriteLine("****************************************************")
 
 Console.ReadKey()
 End Sub
 
 End Module

 

I hope this helps.

Regards,

 

Alex (Alejandro Campos Magencio)