CryptoSys Home > PKI > How to Validate a Certificate Chain

How to Validate a Certificate Chain


We have the X.509 certificate for an end user 'Enid'. Can we trust that Enid's certificate really is the one issued to her?

In this example, Enid's certificate is issued by an "intermediate" authority Ian, whose certificate is in turn issued by the ultimate certification authority (CA), Carl. Carl's certificate is a self-signed root certificate.

+-------------------------------------+
| End user:    Enid                   |
| Certificate: EnidRSASignedByIan.cer |
+-------------------------------------+
                ^
issued by       |
                |
+-------------------------------------+
| Intermediate CA: Ian                |
| Certificate: IanRSASignedByCarl.cer |
+-------------------------------------+
                ^
issued by       |
                |
+-------------------------------------+
| Certification Authority: Carl       |   
| Certificate: CarlRSASelf.cer        |
+-------------------------------------+
                ^
issued by       |
                |
             [Carl]

2109-01-21: This is an update of a page last updated in February 2009. We've cleaned it up and provided up-to-date example X.509 certificates. The original VBA/VB6 code is unchanged.

Like those idiot tests you occasionally see, read everything before doing anything.

Method

To ensure that Enid's certificate is valid, we need to

  1. Make sure that Enid's certificate and every other certificate in the chain has not expired.
  2. Ensure that the signature in each certificate really was created by the holder of the issuing certificate.

As a further check with the root certificate, we can compare its "thumbprint" - the message digest value of the certificate file itself - with a separate value that we have hard-coded somewhere. This prevents someone substituting a false certificate for the root certificate and then re-creating the entire chain.

Example

Public Function TestTheChain() As Boolean
    Dim nRet As Long
    Dim strCertName As String
    Dim strIssuerCert As String
    Dim strThumbPrint As String
    
    ' Chain: [Enid] issued by [Ian] issued by [Carl] self-issued by [Carl].
    ' Given the three certs, can we trust that Enid's certificate really is the one issued to her?
    ' Assumes we trust the CA's certificate and all certificates issued by it.
    ' Does not deal with certificate revokation (CRL) issues.
    
    ' 1. Is Enid's certificate currently valid?
    strCertName = "EnidRSASignedByIan.cer"
    nRet = X509_CertIsValidNow(strCertName, 0)
    If nRet > 0 Then
        Debug.Print "Error: " & nRet & " " & pkiGetLastError()
    ElseIf nRet < 0 Then
        MsgBox "Validation error: cert '" & strCertName & "' is no longer valid at this time.", vbCritical
        Exit Function
    Else
        Debug.Print "Cert '" & strCertName & "' is currently valid."
    End If
    
    ' 2. Was Enid's certificate issued by Ian?
    strIssuerCert = "IanRSASignedByCarl.cer"
    nRet = X509_VerifyCert(strCertName, strIssuerCert, 0)
    If nRet > 0 Then
        Debug.Print "Error: " & nRet & " " & pkiGetLastError()
    ElseIf nRet < 0 Then
        MsgBox "Validation error: cert '" & strCertName & "' was not issued by '" & strIssuerCert & "'.", vbCritical
        Exit Function
    Else
        Debug.Print "Verified that cert '" & strCertName & "' was issued by '" & strIssuerCert & "'."
    End If
    
    ' Continuing up the chain...
    ' 3. Is Ian's certificate currently valid?
    strCertName = "IanRSASignedByCarl.cer"
    nRet = X509_CertIsValidNow(strCertName, 0)
    If nRet > 0 Then
        Debug.Print "Error: " & nRet & " " & pkiGetLastError()
    ElseIf nRet < 0 Then
        MsgBox "Validation error: cert '" & strCertName & "' is no longer valid at this time.", vbCritical
        Exit Function
    Else
        Debug.Print "Cert '" & strCertName & "' is currently valid."
    End If
    
    ' 4. Was Ian's certificate issued by Carl?
    strIssuerCert = "CarlRSASelf.cer"
    nRet = X509_VerifyCert(strCertName, strIssuerCert, 0)
    If nRet > 0 Then
        Debug.Print "Error: " & nRet & " " & pkiGetLastError()
    ElseIf nRet < 0 Then
        MsgBox "Validation error: cert '" & strCertName & "' was not issued by '" & strIssuerCert & "'.", vbCritical
        Exit Function
    Else
        Debug.Print "Verified that cert '" & strCertName & "' was issued by '" & strIssuerCert & "'."
    End If
    
    ' At the top of the chain we have a self-signed certificate...
    ' 5. Is Carl's certificate currently valid?
    strCertName = "CarlRSASelf.cer"
    nRet = X509_CertIsValidNow(strCertName, 0)
    If nRet > 0 Then
        Debug.Print "Error: " & nRet & " " & pkiGetLastError()
    ElseIf nRet < 0 Then
        MsgBox "Validation error: cert '" & strCertName & "' is no longer valid at this time.", vbCritical
        Exit Function
    Else
        Debug.Print "Cert '" & strCertName & "' is currently valid."
    End If
    
    ' 6. Was Carl's certificate issued by Carl?
    strIssuerCert = "CarlRSASelf.cer"
    nRet = X509_VerifyCert(strCertName, strIssuerCert, 0)
    If nRet > 0 Then
        Debug.Print "Error: " & nRet & " " & pkiGetLastError()
    ElseIf nRet < 0 Then
        MsgBox "Validation error: cert '" & strCertName & "' was not issued by '" & strIssuerCert & "'.", vbCritical
        Exit Function
    Else
        Debug.Print "Verified that cert '" & strCertName & "' was issued by '" & strIssuerCert & "'."
    End If
    
    ' Finally, we can hard-code the "thumbprint" (hash digest) of the ultimate CA's certificate
    ' and check that it matches what we have in hand
    ' (you can get this value using CERTMGR.EXE).
    
    ' 7. Is Carl's certificate the one we expected?
    Const HARD_CODED_THUMBPRINT As String = "4110908F77C64C0EDFC2DE6273BFA9A98A9C5CE5"
    strCertName = "CarlRSASelf.cer"
    strThumbPrint = String(PKI_SHA1_CHARS, " ")
    nRet = X509_CertThumb(strCertName, strThumbPrint, Len(strThumbPrint), PKI_HASH_SHA1)
    Debug.Print "ThumbPrint(SHA-1, '" & strCertName & "')=" & strThumbPrint
    If UCase(strThumbPrint) = HARD_CODED_THUMBPRINT Then
        Debug.Print "CA cert's thumbprint matches what we expect."
    Else
        MsgBox "Validation error: cert '" & strCertName & "' does not have the thumbprint we expect.", vbCritical
        Exit Function
    End If
    
    ' If we got to here, we have validated the entire chain
    Debug.Print "OK, certificate chain has been validated."
    
    ' RETURN SUCCESS
    TestTheChain = True
    
End Function

Requirements

The full VBA code is in X509_TestCerts.bas. Test certificates for this example are in X509_TestCerts.zip (3.1 kB, last updated 2019-01-21). Carl's certificate and private key are from RFC 4134 Examples of S/MIME Messages. To run the example code you will need to install the CryptoSys PKI Toolkit available from https://www.cryptosys.net/pki/ and include the module basCrPKI in your VB6/VBA project.

Algorithm

VALIDATE_CERT_CHAIN(certs, root_thumb)

Input:

A sequence certs of X.509 certificate files [cert_1, cert_2, ..., cert_n] where cert_i is issued by the holder of cert_{i+1} and cert_n is a trusted self-signed root certificate; the message digest hash root_thumb of a trusted copy of the root certificate cert_n.

Output:

Returns true if all certificates are valid as at the current date and each certificate cert_i was signed by the holder of cert_{i+1} and the message digest of the file cert_n matches root_thumb. Otherwise returns false.

Actions:

  1. for i=1 to n-1 do:
    1. if cert_i is not currently valid then return false
    2. if cert_i was not signed by the holder of cert_{i+1} then return false
  2. if cert_n is not currently valid OR was not signed by itself then return false
  3. if HASH(cert_n) is not equal to the value of root_thumb then return false
  4. return true

See Also

X509_VerifyCert   X509_CertThumb

Read this first

Update 2019-01-21: Since we wrote this page over 10 years ago, we introduced the X509_ValidatePath function in CryptoSys PKI that does all this in one go.

nRet = X509_ValidatePath("EnidRSASignedByIan.cer;IanRSASignedByCarl.cer;CarlRSASelf.cer", "CarlRSASelf.cer", 0) 
Debug.Print "X509_ValidatePath returns " & nRet & " (expected 0)" 

or in C#

r = X509.ValidatePath("EnidRSASignedByIan.cer;IanRSASignedByCarl.cer;CarlRSASelf.cer", "CarlRSASelf.cer", 0);
Console.WriteLine("X509_ValidatePath returns {0} (expected 0)", r);

Contact

For more information, please send us a message.

This page last updated: 21 January 2019