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.
To ensure that Enid's certificate is valid, we need to
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.
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
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.
VALIDATE_CERT_CHAIN(certs, root_thumb)
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.
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.
X509_VerifyCert
X509_CertThumb
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);
For more information, please send us a message.
This page last updated: 21 January 2019