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