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 ValueType: System.Booleantrue 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)