[ Team LiB ] Previous Section Next Section

10.11 Obtaining CRLs with CryptoAPI

10.11.1 Problem

You have a certificate that you want to verify, as well as the certificate that was used to issue it, but you need to check the issuing authority's CRL to make sure that the certificate has not been revoked. We cover how to use a CRL once you have it in Recipe 10.6—but how do you get it in the first place?

10.11.2 Solution

Obtaining a CRL with CryptoAPI follows the same basic procedure as doing so with OpenSSL (see Recipe 10.10); the only difference is in the functions used to perform the work. We only provide support for retrieving CRLs via HTTP in this recipe and in Recipe 10.10. We will use the WinInet API (see Recipe 9.4) and the relevant CryptoAPI functions to create a CryptoAPI CRL_CONTEXT object from data retrieved from a CA.

10.11.3 Discussion

For Windows, we mostly duplicate the table that was built in Recipe 10.10, but for simplicity, we strip from the data structure some members we will not be using. The name of the CA, the length of the fingerprint, and the URL to the OCSP for the CA are all omitted, leaving only the fingerprint and URL to retrieve the CRL.

#include <windows.h>
#include <wincrypt.h>
#include <wininet.h>
   
typedef struct {
  BYTE   *pbFingerPrint;
  LPSTR  lpszCRLURL;
} SPC_CACERT;
   
static SPC_CACERT rgLookupTable[  ] = {
  { "\x67\xcb\x9d\xc0\x13\x24\x8a\x82\x9b\xb2\x17\x1e\xd1\x1b\xec\xd4",
    "http://crl.geotrust.com/crls/secureca.crl" },
  { "\x8f\x5d\x77\x06\x27\xc4\x98\x3c\x5b\x93\x78\xe7\xd7\x7d\x9b\xcc",
    "http://crl.geotrust.com/crls/globalca1.crl" },
  { "\x64\x9c\xef\x2e\x44\xfc\xc6\x8f\x52\x07\xd0\x51\x73\x8f\xcb\x3d",
    "http://crl.geotrust.com/crls/ebizca1.crl" },
  { "\xaa\xbf\xbf\x64\x97\xda\x98\x1d\x6f\xc6\x08\x3a\x95\x70\x33\xca",
    "http://crl.geotrust.com/crls/ebiz.crl" },
  { "\x74\x7b\x82\x03\x43\xf0\x00\x9e\x6b\xb3\xec\x47\xbf\x85\xa5\x93",
    "http://crl.verisign.com/RSASecureServer.crl" },
  { "\xc5\x70\xc4\xa2\xed\x53\x78\x0c\xc8\x10\x53\x81\x64\xcb\xd0\x1d",
    "https://www.thawte.com/cgi/lifecycle/getcrl.crl?skeyid=%07%15%28mps%AA"
     "%B2%8A%7C%0F%86%CE8%93%008%05%8A%B1" },
  { "\x8d\x26\xff\x2f\x31\x6d\x59x\29\xdd\xe6\x36\xa7\xe2\xce\x64\x25",
    "https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class1.crl?Page=GetCrl"
     "&crl=2" },
  { "\xb8\x16\x33\x4c\x4c\x4c\xf2\xd8\xd3\x4d\x06\xb4\xa6\x58\x40\x03",
    "https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class2.crl?Page=GetCrl"
     "&crl=3" },
  { "\x5f\x94\x4a\x73\x22\xb8\xf7\xd1\x31\xec\x59\x39\xf7\x8e\xfe\x6e",
    "https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class3.crl?Page=GetCrl"
     "&crl=4" },
  { "\x0e\xfa\x4b\xf7\xd7\x60\xcd\x65\xf7\xa7\x06\x88\x57\x98\x62\x39",
    "https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class4.crl?Page=GetCrl"
     "&crl=5" },
  { "\xa7\xf2\xe4\x16\x06\x41\x11\x60\x30\x6b\x9c\xe3\xb4\x9c\xb0\xc9",
    "http://crl.usertrust.com/UTN-UserFirst-Object.crl" },
  { "\xbf\x60\x59\xa3\x5b\xba\xf6\xa7\x76\x42\xda\x6f\x1a\x7b\x50\xcf",
    "http://crl.usertrust.com/UTN-UserFirst-NetworkApplications.crl" },
  { "\x4c\x56\x41\xe5\x0d\xbb\x2b\xe8\xca\xa3\xed\x18\x08\xad\x43\x39",
    "http://crl.usertrust.com/UTN-UserFirst-Hardware.crl" },
  { "\xd7\x34\x3d\xef\x1d\x27\x09\x28\xe1\x31\x02\x5b\x13\x2b\xdd\xf7",
    "http://crl.usertrust.com/UTN-UserFirst-ClientAuthenticationandEmail.crl" },
  { "\xb3\xa5\x3e\x77\x21\x6d\xac\x4a\xc0\xc9\xfb\xd5\x41\x3d\xca\x06",
    "http://crl.usertrust.com/UTN-DataCorpSGC.crl" },
  { "\x65\x58\xab\x15\xad\x57\x6c\x1e\xa8\xa7\xb5\x69\xac\xbf\xff\xeb",
    "http://www.valicert.com/repository/ValiCert%20Calss%201%20Policy%20Val"
     "idation%20Authority.crl" },
  { "\x51\x86\xe8\x1f\xbc\xb1\xc3\x71\xb5\x18\x10\xdb\x5f\xdc\xf6\x20",
    "http://crl.verisign.com/pca1.1.1.crl" },
  { "\x97\x60\xe8\x57\x5f\xd3\x50\x47\xe5\x43\x0c\x94\x36\x8a\xb0\x62",
    "http://crl.verisign.com/pca1.1.1.crl" },
  { "\xf2\x7d\xe9\x54\xe4\xa3\x22\x0d\x76\x9f\xe7\x0b\xbb\xb3\x24\x2b",
    "http://crl.verisign.com/pca1-g2.crl" },
  { "\xdb\x23\x3d\xf9\x69\xfa\x4b\xb9\x95\x80\x44\x73\x5e\x7d\x41\x83",
    "http://crl.verisign.com/pca1-g2.crl" },
  { "\xec\x40\x7d\x2b\x76\x52\x67\x05\x2c\xea\xf2\x3a\x4f\x65\xf0\xd8",
    "http://crl.verisign.com/pca2.1.1.crl" },
  { "\xb3\x9c\x25\xb1\xc3\x2e\x32\x53\x80\x15\x30\x9d\x4d\x02\x77\x3e",
    "http://crl.verisign.com/pca2.1.1.crl" },
  { "\x74\xa8\x2c\x81\x43\x2b\x35\x60\x9b\x78\x05\x6b\x58\xf3\x65\x82",
    "http://crl.verisign.com/pca2-g2.crl" },
  { "\x2d\xbb\xe5\x25\xd3\xd1\x65\x82\x3a\xb7\x0e\xfa\xe6\xeb\xe2\xe1",
    "http://crl.verisign.com/pca2-g2.crl" },
  { "\x78\x2a\x02\xdf\xdb\x2e\x14\xd5\xa7\x5f\x0a\xdf\xb6\x8e\x9c\x5d",
    "http://crl.verisign.com/pca3.1.1.crl" },
  { "\x10\xfc\x63\x5d\xf6\x26\x3e\x0d\xf3\x25\xbe\x5f\x79\xcd\x67\x67",
    "http://crl.verisign.com/pca3.1.1.crl" },
  { "\xc4\x63\xab\x44\x20\x1c\x36\xe4\x37\xc0\x5f\x27\x9d\x0f\x6f\x6e",
    "http://crl.verisign.com/pca3-g2.crl" },
  { "\xa2\x33\x9b\x4c\x74\x78\x73\xd4\x6c\xe7\xc1\xf3\x8d\xcb\x5c\xe9",
    "http://crl.verisign.com/pca3-g2.crl" },
  { "\xdd\x75\x3f\x56\xbf\xbb\xc5\xa1\x7a\x15\x53\xc6\x90\xf9\xfb\xcc",
    "http://crl.verisign.com/Class3SoftwarePublishers.crl" },
  { "\x71\x1f\x0e\x21\xe7\xaa\xea\x32\x3a\x66\x23\xd3\xab\x50\xd6\x69",
    "http://crl.verisign.com/Class2SoftwarePublishers.crl" },
  { 0, 0 }
};

The worker function GetDistributionPoint( ) will look for a cRLDistributionPoints extension in a certificate that has a URL. If the extension is present, CryptoAPI will return the data in Unicode, so we need to convert it back down to the single-byte OEM codepage.

static LPSTR make_thin(LPWSTR wstr) {
  int   len;
  DWORD dwFlags;
  LPSTR str;
   
  dwFlags = WC_COMPOSITECHECK | WC_DISCARDNS;
  if (!(len = WideCharToMultiByte(CP_OEMCP, dwFlags, wstr, -1, 0, 0, 0, 0)))
    return 0;
  if (!(str = (LPSTR)LocalAlloc(LMEM_FIXED, len))) return 0;
  WideCharToMultiByte(CP_OEMCP, dwFlags, wstr, -1, str, len, 0, 0);
  return str;
}
   
static LPSTR GetDistributionPoint(PCCERT_CONTEXT pCertContext) {
  DWORD                 cbStructInfo, i, j;
  LPSTR                 lpszURL;
  LPVOID                pvStructInfo;
  CERT_EXTENSION        *pExtension;
  CERT_ALT_NAME_INFO    *pNameInfo;
  CRL_DIST_POINTS_INFO  *pInfo;
   
  pExtension = CertFindExtension(szOID_CRL_DIST_POINTS,
                                 pCertContext->pCertInfo->cExtension,
                                 pCertContext->pCertInfo->rgExtension);
  if (!pExtension) return 0;
   
  if (!CryptDecodeObject(X509_ASN_ENCODING, szOID_CRL_DIST_POINTS,
      pExtension->Value.pbData, pExtension->Value.cbData, 0, 0, &cbStructInfo))
    return 0;
  if (!(pvStructInfo = LocalAlloc(LMEM_FIXED, cbStructInfo))) return 0;
  CryptDecodeObject(X509_ASN_ENCODING, szOID_CRL_DIST_POINTS,
                    pExtension->Value.pbData, pExtension->Value.cbData, 0,
                    pvStructInfo, &cbStructInfo);
  pInfo = (CRL_DIST_POINTS_INFO *)pvStructInfo;
  for (i = 0;  i < pInfo->cDistPoint;  i++) {
    if (pInfo->rgDistPoint[i].DistPointName.dwDistPointNameChoice =  =
        CRL_DIST_POINT_FULL_NAME) {
      pNameInfo = &pInfo->rgDistPoint[i].DistPointName.FullName;
      for (j = 0;  j < pNameInfo->cAltEntry;  i++) {
        if (pNameInfo->rgAltEntry[j].dwAltNameChoice =  = CERT_ALT_NAME_URL) {
          if (!(lpszURL = make_thin(pNameInfo->rgAltEntry[i].pwszURL))) break;
          LocalFree(pvStructInfo);
          return lpszURL;
        }
      }
    }
  }
   
  LocalFree(pvStructInfo);
  return 0;
}

The SpcLookupCACert( ) function computes the fingerprint of the specified certificate and tries to match it with a fingerprint in the table of CA certificates and CRL URLs that we've already defined. If a match is found, the function returns a pointer to the matching entry. We will be using MD5 for computing the fingerprint, so we know that the size of the fingerprint will always be 16 bytes. (Note that we have essentially taken the SpcFingerPrintCert( ) and SpcFingerPrintEqual( ) functions from Recipe 10.9, stripped them down a bit, and combined them here.)

SPC_CACERT *SpcLookupCACert(PCCERT_CONTEXT pCertContext) {
  SPC_CACERT  *pCACert;
  BYTE        pbFingerPrint[16];  /* MD5 is 128 bits or 16 bytes */
  DWORD       cbFingerPrint;
   
  /* Compute the fingerprint of the certificate */
  cbFingerPrint = sizeof(pbFingerPrint);
  CryptHashCertificate(0, CALG_MD5, 0, pCertContext->pbCertEncoded,
                       pCertContext->cbCertEncoded, pbFingerPrint,
                       &cbFingerPrint);
   
  /* Compare the computed certificate against those in our lookup table */
  for (pCACert = rgLookupTable;  pCACert->pbFingerPrint;  pCACert++) {
    if (!memcmp(pCACert->pbFingerPrint, pbFingerPrint, cbFingerPrint))
      return pCACert;
  }
  return 0;
}

SpcGetCertCRLURL( ) attempts to find the URL for the CRL for a certificate. It first checks the subject's certificate for an RFC 3280 cRLDistributionPoints extension using the GetDistributionPoint( ) worker function. If the subject certificate does not have one, the function checks the issuer's certificate. If neither certificate contains a cRLDistributionPoints extension, it checks the issuer certificate's fingerprint against the table of CA fingerprints and CRL URLs using SpcLookupCACert( ). If a URL cannot be determined, SpcGetCertCRLURL( ) returns NULL.

LPSTR SpcGetCertCRLURL(PCCERT_CONTEXT pSubject, PCCERT_CONTEXT pIssuer,
                       BOOL bLookupOnly) {
  LPSTR      lpszURL;
  SPC_CACERT *pCACert;
   
  if (!bLookupOnly) {
    if (pSubject && (lpszURL = GetDistributionPoint(pSubject)) != 0)
      return lpszURL;
    if (pIssuer && (lpszURL = GetDistributionPoint(pIssuer)) != 0)
      return lpszURL;
  }
   
  /* Get the fingerprint of the cert's issuer, and look it up in a table */
  if (pIssuer) {
    if (!(pCACert = SpcLookupCACert(pIssuer))) return 0;
    if (pCACert->lpszCRLURL) {
      lpszURL = (LPSTR)LocalAlloc(LMEM_FIXED, lstrlenA(pCACert->lpszCRLURL) + 1);
      if (!lpszURL) return 0;
      lstrcpy(lpszURL, pCACert->lpszCRLURL);
      return lpszURL;
    }
  }
   
  return 0;
}

The worker function RetrieveWebData( ) is a wrapper around the WinInet API to retrieve the CRL data from an HTTP or FTP server, depending on the URL. It simply establishes a connection to the server, retrieves the data if it can, and returns the data that was retrieved to the caller. The CRL data is dynamically allocated with LocalAlloc( ), and it is expected that the caller will free the data with LocalFree( ) when it is no longer needed. (The WinInet API is discussed in detail in Recipe 9.4.)

static BYTE *RetrieveWebData(LPSTR lpszURL, DWORD *lpdwDataLength) {
  DWORD     dwContentLength, dwFlags, dwNumberOfBytesRead,
            dwNumberOfBytesToRead;
  LPVOID    lpBuffer, lpFullBuffer, lpNewBuffer;
  HINTERNET hRequest, hSession;
   
  hSession = InternetOpen(TEXT("Secure Programming Cookbook Recipe 10.11"),
                          INTERNET_OPEN_TYPE_PROXY, 0, 0, 0);
  if (!hSession) return 0;
   
  dwFlags = INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
            INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_NO_COOKIES |
            INTERNET_FLAG_NO_UI | INTERNET_FLAG_PASSIVE;
  hRequest = InternetOpenUrl(hSession, lpszURL, 0, 0, dwFlags, 0);
  if (!hRequest) {
    InternetCloseHandle(hSession);
    return 0;
  }
   
  dwContentLength = 0;
  dwNumberOfBytesToRead = 1024;
  lpFullBuffer = lpBuffer = LocalAlloc(LMEM_FIXED, dwNumberOfBytesToRead);
  while (InternetReadFile(hRequest, lpBuffer, dwNumberOfBytesToRead,
                          &dwNumberOfBytesRead)) {
    dwContentLength = dwContentLength + dwNumberOfBytesRead;
    if (dwNumberOfBytesRead != dwNumberOfBytesToRead) break;
    if (!(lpNewBuffer = LocalReAlloc(lpFullBuffer, dwContentLength +
                                     dwNumberOfBytesToRead, 0))) {
      LocalFree(lpFullBuffer);
      InternetCloseHandle(hRequest);
      InternetCloseHandle(hSession);
      return 0;
    }
    lpFullBuffer = lpNewBuffer;
    lpBuffer = (LPVOID)((LPBYTE)lpFullBuffer + dwContentLength);
  }
   
  if ((lpNewBuffer = LocalReAlloc(lpFullBuffer, dwContentLength, 0)) != 0)
    lpFullBuffer = lpNewBuffer;
  InternetCloseHandle(hRequest);
  InternetCloseHandle(hSession);
  *lpdwDataLength = dwContentLength;
  return (BYTE *)lpFullBuffer;
}

The primary function used in this recipe is SpcRetrieveCRL( ). It ties all of the other functions together in a neat little package, returning a CRL_CONTEXT object to the caller if a CRL can be successfully obtained using the information from the subject and issuer certificates that are required as arguments. SpcRetrieveCRL( ) uses the URL information from cRLDistributionPoints extensions in either certificate before consulting the internal table of CA fingerprints and CRL URLs. Unfortunately, the cRLDistributionPoints extension often contains a URL that is invalid, so this case is handled by falling back on the table lookup if the data cannot be retrieved from the cRLDistributionPoints information.

If the function is successful, it returns a CRL_CONTEXT object created using CryptoAPI. When the object is no longer needed, it should be destroyed using CertFreeCRLContext( ). If a CRL cannot be created for some reason, NULL is returned, and the Win32 function GetLastError( ) can be used to determine what went wrong.

PCCRL_CONTEXT SpcRetrieveCRL(PCCERT_CONTEXT pSubject, PCCERT_CONTEXT pIssuer) {
  BYTE          *pbData;
  DWORD         cbData;
  LPSTR         lpszURL, lpszSecondURL;
  PCCRL_CONTEXT pCRL;
   
  if (!(lpszURL = SpcGetCertCRLURL(pSubject, pIssuer, FALSE))) return 0;
  if (!(pbData = RetrieveWebData(lpszURL, &cbData))) {
    lpszSecondURL = SpcGetCertCRLURL(pSubject, pIssuer, TRUE);
    if (!lpszSecondURL || !lstrcmpA(lpszURL, lpszSecondURL)) {
      if (lpszSecondURL) LocalFree(lpszSecondURL);
      LocalFree(lpszURL);
      return 0;
    }
    pbData = RetrieveWebData(lpszSecondURL, &cbData);
    LocalFree(lpszSecondURL);
  }
   
  if (pbData) {
    pCRL = CertCreateCRLContext(X509_ASN_ENCODING, pbData, cbData);
    LocalFree(pbData);
  }
  LocalFree(lpszURL);
  return pCRL;
}

10.11.4 See Also

    [ Team LiB ] Previous Section Next Section