10.6 Performing X.509 Certificate Verification with CryptoAPI

10.6.1 Problem

You have an X.509 certificate, and you want to verify its validity using Microsoft's CryptoAPI on Windows.

10.6.2 Solution

CryptoAPI represents an X.509 certificate using a CERT_CONTEXT object. Another object, referenced by a HCERTSTORE handle, must be created to hold the certificates that will be required for verification, as well as any certificate revocation lists (CRLs) that may be necessary. The actual certificate verification is performed by calling the CertGetIssuerCertificateFromStore( ) function for each certificate in the hierarchy. This function will verify the signature, certificate validity times, and revocation status of each certificate as it obtains the issuer for each call. The last certificate in the hierarchy will have no issuing certificate and should be self-signed.

10.6.3 Discussion

Call the CertGetIssuerCertificateFromStore( ) function for each certificate in the hierarchy, beginning with the subject certificate at the end of the chain. Each time CertGetIssuerCertificateFromStore( ) is called, CryptoAPI will attempt to locate the issuer of the subject certificate passed into it. If the issuer certificate is found, the signature of the subject certificate will be verified with the public key of the issuer certificate. In addition, time validity checks will be performed on the subject certificate, and the subject certificate will be compared against the issuer's CRL if it is present in the store.

#include <windows.h>
#include <wincrypt.h>
   
BOOL SpcVerifyCert(HCERTSTORE hCertStore, PCCERT_CONTEXT pSubjectContext) {
  DWORD           dwFlags;
  PCCERT_CONTEXT  pIssuerContext;
   
  if (!(pSubjectContext = CertDuplicateCertificateContext(pSubjectContext)))
    return FALSE;
  do {
    dwFlags = CERT_STORE_REVOCATION_FLAG | CERT_STORE_SIGNATURE_FLAG |
              CERT_STORE_TIME_VALIDITY_FLAG;
    pIssuerContext = CertGetIssuerCertificateFromStore(hCertStore,
                                                pSubjectContext, 0, &dwFlags);
    CertFreeCertificateContext(pSubjectContext);
    if (pIssuerContext) {
      pSubjectContext = pIssuerContext;
      if (dwFlags & CERT_STORE_NO_CRL_FLAG)
        dwFlags &= ~(CERT_STORE_NO_CRL_FLAG | CERT_STORE_REVOCATION_FLAG);
      if (dwFlags) break;
    } else if (GetLastError(  ) =  = CRYPT_E_SELF_SIGNED) return TRUE;
  } while (pIssuerContext);
  return FALSE;
}

Every certificate returned by CertGetIssuerCertificateFromStore( ) must be freed with a call to CertFreeCertificateContext( ). To make things a bit simpler, a copy of the original subject certificate is made so that the subject certificate can always be freed after the call to CertGetIssuerCertificateFromStore( ). If an issuer certificate is returned, the subject becomes the issuer for the next iteration through the loop.

When CertGetIssuerCertificateFromStore( ) cannot find the issuing certificate for the subject certificate in the store, it returns NULL. This could mean that the end of the certificate hierarchy has been reached, in which case GetLastError( ) will return CRYPT_E_SELF_SIGNED because the root certificate in any hierarchy must always be self-signed. A NULL return from CertGetIssuerCertificateFromStore( ) might also indicate that there may be an issuer certificate for the subject certificate, but that one wasn't present in the certificate store; this is an error condition that results in the verification failure of the subject certificate.

The call to CertGetIssuerCertificateFromStore( ) requires a set of flags to be passed into it that determines what verification checks are to be performed on the subject certificate. Upon return from the call, this set of flags is modified, leaving the bits set for the types of verification checks that failed. SpcVerifyCert( ) checks the set of flags after the successful return from CertGetIssuerCertificateFromStore( ) to see if CERT_STORE_NO_CRL_FLAG is set. If it is, this indicates that no CRL could be found in the store against which the subject certificate could be compared. At this point, the flags indicating failure as a result of there being no CRL are cleared. If any flags remain set, this means that verification of the subject certificate failed; the loop is terminated, and failure is returned.

10.6.3.1 CryptoAPI certificate stores

Several special certificate stores are available for use. In addition, private stores can be created that reside in memory, in the registry, or in a disk file. To use one of the special certificate stores, use the CryptoAPI function CertOpenSystemStore( ). This function requires a handle to a Cryptographic Services Provider (CSP) and the name of the certificate store to open. In the majority of cases, the CSP handle can be passed as NULL, in which case the default CSP will be used. One of the names listed in Table 10-2 may be opened for use.

Table 10-2. System certificate stores and their contents

Certificate store name

Types of certificates in the store

MY

Contains certificates that are owned by the current user. For each certificate in this store, the associated private key is also available.

CA

Contains CA certificates that are not self-signed root certificates. These certificates are capable of issuing certificates.

ROOT

Contains root CA certificates that are trusted. All of the certificates in this store should be self-signed.

SPC

Contains trusted software publisher certificates. The certificates in this store are used by Microsoft's Authenticode.

For the purposes of verification using SpcVerifyCert( ) as presented, you'll need to create a temporary certificate store that contains all the certificates that will be needed to verify a subject certificate. At a minimum, the certificate that you want to verify must be in the store, but verification will only succeed if the only certificate in the store is the subject certificate and is self-signed, which in the vast majority of cases isn't all that useful.

If you do not have all the certificates and need to use certificates from one of the system stores, a copy of the needed certificate from the system store can be made for insertion into the temporary store being used for verification. Otherwise, certificates in memory as CERT_CONTEXT objects can be added to the temporary store, or encoded certificates residing in memory as a blob (binary large object) can be added.

#include <windows.h>
#include <wincrypt.h>
   
static PCCERT_CONTEXT FindIssuerInSystemStore(LPCTSTR pszStoreName,
                                             PCCERT_CONTEXT pSubjectContext) {
  HCERTSTORE     hCertStore;
  PCCERT_CONTEXT pIssuerContext
   
  if (!(hCertStore = CertOpenSystemStore(0, pszStoreName))) return 0;
  pIssuerContext = CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING, 0,
                                         CERT_FIND_ISSUER_OF, pSubjectContext, 0);
  CertCloseStore(hCertStore, 0);
  return pIssuerContext;
}
   
static LPCTSTR SpcSystemStoreList[  ] = {
  TEXT("MY"), TEXT("CA"), TEXT("ROOT"), TEXT("SPC"), 0
};
   
HCERTSTORE SpcNewStoreForCert(PCCERT_CONTEXT pSubjectContext) {
  LPCTSTR        pszStoreName;
  HCERTSTORE     hCertStore;
  PCCERT_CONTEXT pIssuerContext;
   
  /* First create an in-memory store, and add the subject certificate to it */
  if (!(hCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, 0))) return 0;
  if (!CertAddCertificateContextToStore(hCertStore, pSubjectContext,
                                       CERT_STORE_ADD_REPLACE_EXISTING, 0)) {
    CertCloseStore(hCertStore, 0);
    return 0;
  }
   
  pSubjectContext = CertDuplicateCertificateContext(pSubjectContext);
  while (!CertCompareCertificateName(X509_ASN_ENCODING,
         pSubjectContext->pCertInfo->Issuer, pSubjectContext->pCertInfo->Subject)){
    for (pszStoreName = SpcSystemStoreList;  pszStoreName;  pszStoreName++) {
      pIssuerContext = FindIssuerInSystemStore(pszStoreName, pSubjectContext);
      if (pIssuerContext) {
        if (!CertAddCertificateContextToStore(hCertStore, pIssuerContext,
                                             CERT_STORE_ADD_REPLACE_EXISTING, 0)) {
          CertFreeCertificateContext(pSubjectContext);
          CertFreeCertificateContext(pIssuerContext);
          CertCloseStore(hCertStore, 0);
          return 0;
        }
        CertFreeCertificateContext(pSubjectContext);
        pSubjectContext = pIssuerContext;
        break;
      }
    }
    if (!pszStoreName) {
      CertFreeCertificateContext(pSubjectContext);
      CertCloseStore(hCertStore, 0);
      return 0;
    }
  }
  CertFreeCertificateContext(pSubjectContext);
  return hCertStore;
}

The SpcNewStoreForCert( ) function creates a temporary in-memory certificate store that can be used with SpcVerifyCert( ). Only a single argument is required: the subject certificate that is, presumably, at the end of a certificate hierarchy. The subject certificate is added to the new certificate store, and for each issuing certificate in the hierarchy, the system stores are searched for a copy of the certificate. If one cannot be found, the new certificate store is destroyed and SpcNewStoreForCert( ) returns NULL; otherwise, the found certificate will be added to the new certificate store.

Once the store has been created, it can now be passed directly into the SpcVerifyCert( ) function, along with the subject certificate to be verified. If there are CRLs for any of the certificates in the hierarchy, add them to the store before calling SpcVerifyCert( ) (see Recipe 10.11 for obtaining CRLs with CryptoAPI). You can enumerate the contents of the certificate store created by SpcNewStoreForCert( ) using CertEnumCertificatesInStore( ):

BOOL           bResult;
HCERTSTORE     hCertStore;
PCCRL_CONTEXT  pCRLContext;
PCCERT_CONTEXT pCertContext = 0;
   
if (!(hCertStore = SpcNewStoreForCert(pSubjectContext))) {
  /* handle an error condition--could not create the store */
  abort(  );
}
while ((pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext))) {
  /* do something with the certificate retrieved from the store.
   * if an error occurs, and enumeration must be terminated prematurely, the last
   * certificate retrieved must be freed manually.
   *
   * For example, attempt to retrieve the CRL for the certificate using the code
   * the can be found in Recipe 10.11.  If no CRL can be retrieved, or the CRL
   * cannot be added to the certificate store, consider it a failure and break
   * out of the enumeration.
   */
  if (!(pCRLContext = SpcRetrieveCRL(pCertContext, 0)) ||
      !CertAddCRLContextToStore(hCertStore, pCRLContext,
                                CERT_ADD_USE_EXISTING, 0)) {
    if (pCRLContext) CertFreeCRLContext(pCRLContext);
    break;
  }
  CertFreeCRLContext(pCRLContext);
}
if (pCertContext) {
  CertFreeCertificateContext(pCertContext);
  CertCloseStore(hCertStore, 0);
  abort(  );
}
bResult = SpcVerifyCert(hCertStore, pSubjectContext);
CertCloseStore(hCertStore, 0);
return bResult;

10.6.4 See Also

Recipe 10.11