|
|
//+-------------------------------------------------------------------------n-
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1996 - 1999
//
// File: crl.cpp
//
// Contents: Cert Server CRL processing
//
//---------------------------------------------------------------------------
#include <pch.cpp>
#pragma hdrstop
#include <stdio.h>
#include <esent.h>
#include "cscom.h"
#include "csprop.h"
#include "dbtable.h"
#include "resource.h"
#include "elog.h"
#include "certlog.h"
#include <winldap.h>
#include "csldap.h"
#include "cainfop.h"
#define __dwFILE__ __dwFILE_CERTSRV_CRL_CPP__
HANDLE g_hCRLManualPublishEvent = NULL;
FILETIME g_ftCRLNextPublish; FILETIME g_ftDeltaCRLNextPublish;
BOOL g_fCRLPublishDisabled = FALSE; // manual publishing always allowed
BOOL g_fDeltaCRLPublishDisabled = FALSE; // controls manual publishing, too
DWORD g_dwCRLFlags = CRLF_DELETE_EXPIRED_CRLS; LDAP *g_pld = NULL;
typedef struct _CSMEMBLOCK { struct _CSMEMBLOCK *pNext; BYTE *pbFree; DWORD cbFree; } CSMEMBLOCK;
#define CBMEMBLOCK 4096
typedef struct _CSCRLELEMENT { USHORT usRevocationReason; USHORT uscbSerialNumber; BYTE *pbSerialNumber; FILETIME ftRevocationDate; } CSCRLELEMENT;
// size the structure just under CBMEMBLOCK to keep it from being just over
// a page size.
#define CCRLELEMENT ((CBMEMBLOCK - 2 * sizeof(DWORD)) / sizeof(CSCRLELEMENT))
typedef struct _CSCRLBLOCK { struct _CSCRLBLOCK *pNext; DWORD cCRLElement; CSCRLELEMENT aCRLElement[CCRLELEMENT]; } CSCRLBLOCK;
typedef struct _CSCRLREASON { struct _CSCRLREASON *pNext; DWORD RevocationReason; CERT_EXTENSION ExtReason; } CSCRLREASON;
typedef struct _CSCRLPERIOD { LONG lCRLPeriodCount; ENUM_PERIOD enumCRLPeriod; DWORD dwCRLOverlapMinutes; } CSCRLPERIOD;
#ifdef DBG_CERTSRV_DEBUG_PRINT
# define DPT_DATE 1
# define DPT_DELTA 2
# define DPT_DELTASEC 3
# define DPT_DELTAMS 4
# define DBGPRINTTIME(pfDelta, pszName, Type, ft) \
DbgPrintTime((pfDelta), (pszName), __LINE__, (Type), (ft))
VOID DbgPrintTime( OPTIONAL IN BOOL const *pfDelta, IN char const *pszName, IN DWORD Line, IN DWORD Type, IN FILETIME ft) { HRESULT hr; WCHAR *pwszTime = NULL; WCHAR awc[1]; LLFILETIME llft; llft.ft = ft; if (Type == DPT_DATE) { if (0 != llft.ll) { hr = myGMTFileTimeToWszLocalTime(&ft, TRUE, &pwszTime); _PrintIfError(hr, "myGMTFileTimeToWszLocalTime"); } } else { if (DPT_DELTAMS == Type) { llft.ll /= 1000; // milliseconds to seconds
Type = DPT_DELTASEC; } if (DPT_DELTASEC == Type) { llft.ll *= CVT_BASE; // seconds to FILETIME period
} llft.ll = -llft.ll; // FILETIME Period must be negative
if (0 != llft.ll) { hr = myFileTimePeriodToWszTimePeriod( &llft.ft, TRUE, // fExact
&pwszTime); _PrintIfError(hr, "myFileTimePeriodToWszTimePeriod"); } } if (NULL == pwszTime) { awc[0] = L'\0'; pwszTime = awc; }
DBGPRINT(( DBG_SS_CERTSRVI, "%hs(%d):%hs time(%hs): %lx:%08lx %ws\n", "crl.cpp", Line, NULL == pfDelta? "" : (*pfDelta? " Delta CRL" : " Base CRL"), pszName, ft.dwHighDateTime, ft.dwLowDateTime, pwszTime));
//error:
if (NULL != pwszTime && awc != pwszTime) { LocalFree(pwszTime); } }
VOID CertSrvDbgPrintTime( IN char const *pszDesc, IN FILETIME const *pftGMT) { HRESULT hr; WCHAR *pwszTime = NULL; WCHAR awc[1];
hr = myGMTFileTimeToWszLocalTime(pftGMT, TRUE, &pwszTime); _PrintIfError(hr, "myGMTFileTimeToWszLocalTime"); if (S_OK != hr) { awc[0] = L'\0'; pwszTime = awc; } DBGPRINT((DBG_SS_CERTSRV, "%hs: %ws\n", pszDesc, pwszTime));
//error:
if (NULL != pwszTime && awc != pwszTime) { LocalFree(pwszTime); } } #else // DBG_CERTSRV_DEBUG_PRINT
# define DBGPRINTTIME(pfDelta, pszName, Type, ft)
#endif // DBG_CERTSRV_DEBUG_PRINT
HRESULT crlMemBlockAlloc( IN OUT CSMEMBLOCK **ppBlock, IN DWORD cb, OUT BYTE **ppb) { HRESULT hr; CSMEMBLOCK *pBlock = *ppBlock;
*ppb = NULL; cb = POINTERROUND(cb); if (NULL == pBlock || cb > pBlock->cbFree) { pBlock = (CSMEMBLOCK *) LocalAlloc(LMEM_FIXED, CBMEMBLOCK); if (NULL == pBlock) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } pBlock->pNext = *ppBlock; pBlock->pbFree = (BYTE *) Add2Ptr(pBlock, sizeof(CSMEMBLOCK)); pBlock->cbFree = CBMEMBLOCK - sizeof(CSMEMBLOCK); *ppBlock = pBlock; } CSASSERT(cb <= pBlock->cbFree); *ppb = pBlock->pbFree; pBlock->pbFree += cb; pBlock->cbFree -= cb; hr = S_OK;
error: return(hr); }
VOID crlBlockListFree( IN OUT CSMEMBLOCK *pBlock) { CSMEMBLOCK *pBlockNext;
while (NULL != pBlock) { pBlockNext = pBlock->pNext; LocalFree(pBlock); pBlock = pBlockNext; } }
HRESULT crlElementAlloc( IN OUT CSCRLBLOCK **ppBlock, OUT CSCRLELEMENT **ppCRLElement) { HRESULT hr; CSCRLBLOCK *pBlock = *ppBlock;
*ppCRLElement = NULL; if (NULL == pBlock || ARRAYSIZE(pBlock->aCRLElement) <= pBlock->cCRLElement) { pBlock = (CSCRLBLOCK *) LocalAlloc(LMEM_FIXED, sizeof(*pBlock)); if (NULL == pBlock) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } pBlock->pNext = *ppBlock; pBlock->cCRLElement = 0; *ppBlock = pBlock; } CSASSERT(ARRAYSIZE(pBlock->aCRLElement) > pBlock->cCRLElement); *ppCRLElement = &pBlock->aCRLElement[pBlock->cCRLElement++]; hr = S_OK;
error: return(hr); }
VOID crlFreeCRLArray( IN OUT VOID *pvBlockSerial, IN OUT CRL_ENTRY *paCRL) { crlBlockListFree((CSMEMBLOCK *) pvBlockSerial); if (NULL != paCRL) { LocalFree(paCRL); } }
HRESULT crlCreateCRLReason( IN OUT CSMEMBLOCK **ppBlock, IN OUT CSCRLREASON **ppReason, IN DWORD RevocationReason, OUT DWORD *pcExtension, OUT CERT_EXTENSION **ppExtension) { HRESULT hr; CSCRLREASON *pReason = *ppReason; BYTE *pbEncoded = NULL; DWORD cbEncoded;
for (pReason = *ppReason; NULL != pReason; pReason = pReason->pNext) { if (RevocationReason == pReason->RevocationReason) { break; } }
if (NULL == pReason) { if (!myEncodeObject( X509_ASN_ENCODING, X509_ENUMERATED, (const void *) &RevocationReason, 0, CERTLIB_USE_LOCALALLOC, &pbEncoded, &cbEncoded)) { hr = myHLastError(); _JumpError(hr, error, "myEncodeObject"); }
hr = crlMemBlockAlloc( ppBlock, sizeof(CSCRLREASON) + cbEncoded, (BYTE **) &pReason); _JumpIfError(hr, error, "crlMemBlockAlloc");
pReason->pNext = *ppReason; pReason->RevocationReason = RevocationReason; pReason->ExtReason.pszObjId = szOID_CRL_REASON_CODE; pReason->ExtReason.fCritical = FALSE; pReason->ExtReason.Value.pbData = (BYTE *) Add2Ptr(pReason, sizeof(*pReason)); pReason->ExtReason.Value.cbData = cbEncoded; CopyMemory(pReason->ExtReason.Value.pbData, pbEncoded, cbEncoded);
*ppReason = pReason;
//printf("crlCreateCRLReason: new %x cb %x\n", RevocationReason, cbEncoded);
} //printf("crlCreateCRLReason: %x\n", RevocationReason);
CSASSERT(NULL != pReason && RevocationReason == pReason->RevocationReason);
*pcExtension = 1; *ppExtension = &pReason->ExtReason; hr = S_OK;
error: if (NULL != pbEncoded) { LocalFree(pbEncoded); } return(hr); }
// Convert linked list of CRL blocks to an array.
// If the output array pointer is NULL, just free the list.
HRESULT ConvertOrFreeCRLList( IN OUT CSCRLBLOCK **ppBlockCRL, // Freed
IN OUT CSMEMBLOCK **ppBlockReason, // Used to allocate reason extensions
IN DWORD cCRL, OPTIONAL OUT CRL_ENTRY **paCRL) { HRESULT hr; CSCRLREASON *pReasonList = NULL; // linked list of reason extensions
CSCRLBLOCK *pBlockCRL = *ppBlockCRL; CRL_ENTRY *aCRL = NULL; CRL_ENTRY *pCRL; DWORD i;
if (NULL != paCRL) { aCRL = (CRL_ENTRY *) LocalAlloc(LMEM_FIXED, sizeof(aCRL[0]) * cCRL); if (NULL == aCRL) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } }
pCRL = aCRL; while (NULL != pBlockCRL) { CSCRLBLOCK *pBlockCRLNext;
if (NULL != pCRL) { for (i = 0; i < pBlockCRL->cCRLElement; i++) { CSCRLELEMENT *pCRLElement = &pBlockCRL->aCRLElement[i];
pCRL->SerialNumber.pbData = pCRLElement->pbSerialNumber; pCRL->SerialNumber.cbData = pCRLElement->uscbSerialNumber; pCRL->RevocationDate = pCRLElement->ftRevocationDate; pCRL->cExtension = 0; pCRL->rgExtension = NULL;
if (CRL_REASON_UNSPECIFIED != pCRLElement->usRevocationReason) { hr = crlCreateCRLReason( ppBlockReason, &pReasonList, pCRLElement->usRevocationReason, &pCRL->cExtension, &pCRL->rgExtension); _JumpIfError(hr, error, "crlCreateCRLReason"); } pCRL++; } } pBlockCRLNext = pBlockCRL->pNext; LocalFree(pBlockCRL); pBlockCRL = pBlockCRLNext; }
if (NULL != paCRL) { CSASSERT(pCRL == &aCRL[cCRL]); *paCRL = aCRL; aCRL = NULL; } CSASSERT(NULL == pBlockCRL); hr = S_OK;
error: *ppBlockCRL = pBlockCRL; if (NULL != aCRL) { LocalFree(aCRL); } return(hr); }
HRESULT AddCRLElement( IN OUT CSMEMBLOCK **ppBlockSerial, IN OUT CSCRLBLOCK **ppBlockCRL, IN WCHAR const *pwszSerialNumber, IN FILETIME const *pftRevokedEffectiveWhen, IN DWORD RevocationReason) { HRESULT hr; CSCRLELEMENT *pCRLElement; DWORD cbSerial; BYTE *pbSerial = NULL;
hr = crlElementAlloc(ppBlockCRL, &pCRLElement); _JumpIfError(hr, error, "crlElementAlloc");
hr = WszToMultiByteInteger( FALSE, pwszSerialNumber, &cbSerial, &pbSerial); _JumpIfError(hr, error, "WszToMultiByteInteger");
hr = crlMemBlockAlloc(ppBlockSerial, cbSerial, &pCRLElement->pbSerialNumber); _JumpIfError(hr, error, "crlMemBlockAlloc");
CopyMemory(pCRLElement->pbSerialNumber, pbSerial, cbSerial);
pCRLElement->ftRevocationDate = *pftRevokedEffectiveWhen; pCRLElement->usRevocationReason = (USHORT) RevocationReason; pCRLElement->uscbSerialNumber = (USHORT) cbSerial;
CSASSERT(pCRLElement->usRevocationReason == RevocationReason); CSASSERT(pCRLElement->uscbSerialNumber == cbSerial);
error: if (NULL != pbSerial) { LocalFree(pbSerial); } return(hr); }
DWORD g_aColCRL[] = {
#define ICOL_DISPOSITION 0
DTI_REQUESTTABLE | DTR_REQUESTDISPOSITION,
#define ICOL_SERIAL 1
DTI_CERTIFICATETABLE | DTC_CERTIFICATESERIALNUMBER,
#define ICOL_EFFECTIVEWHEN 2
DTI_REQUESTTABLE | DTR_REQUESTREVOKEDEFFECTIVEWHEN,
#define ICOL_REASON 3
DTI_REQUESTTABLE | DTR_REQUESTREVOKEDREASON, };
HRESULT BuildCRLList( IN BOOL fDelta, IN DWORD iKey, OPTIONAL IN FILETIME const *pftQueryMinimum, IN FILETIME const *pftThisPublish, IN FILETIME const *pftLastPublishBase, IN OUT DWORD *pcCRL, IN OUT CSCRLBLOCK **ppBlockCRL, IN OUT CSMEMBLOCK **ppBlockSerial) { HRESULT hr; CERTVIEWRESTRICTION acvr[5]; CERTVIEWRESTRICTION *pcvr; IEnumCERTDBRESULTROW *pView = NULL; DWORD celtFetched; DWORD NameIdMin; DWORD NameIdMax; DWORD i; BOOL fEnd; CERTDBRESULTROW aResult[10]; BOOL fResultActive = FALSE; DWORD cCRL = *pcCRL; CSCRLBLOCK *pBlockCRL = *ppBlockCRL; CSMEMBLOCK *pBlockSerial = *ppBlockSerial;
DBGPRINTTIME(NULL, "*pftThisPublish", DPT_DATE, *pftThisPublish);
// Set up restrictions as follows:
pcvr = acvr;
// Request.RevokedEffectiveWhen <= *pftThisPublish (indexed column)
pcvr->ColumnIndex = DTI_REQUESTTABLE | DTR_REQUESTREVOKEDEFFECTIVEWHEN; pcvr->SeekOperator = CVR_SEEK_LE; pcvr->SortOrder = CVR_SORT_DESCEND; pcvr->pbValue = (BYTE *) pftThisPublish; pcvr->cbValue = sizeof(*pftThisPublish); pcvr++;
// Cert.NotAfter >= *pftLastPublishBase
if (0 == (CRLF_PUBLISH_EXPIRED_CERT_CRLS & g_dwCRLFlags)) { pcvr->ColumnIndex = DTI_CERTIFICATETABLE | DTC_CERTIFICATENOTAFTERDATE; pcvr->SeekOperator = CVR_SEEK_GE; pcvr->SortOrder = CVR_SORT_NONE; pcvr->pbValue = (BYTE *) pftLastPublishBase; pcvr->cbValue = sizeof(*pftLastPublishBase); pcvr++; }
// NameId >= MAKECANAMEID(iCert == 0, iKey)
NameIdMin = MAKECANAMEID(0, iKey); pcvr->ColumnIndex = DTI_CERTIFICATETABLE | DTC_CERTIFICATEISSUERNAMEID; pcvr->SeekOperator = CVR_SEEK_GE; pcvr->SortOrder = CVR_SORT_NONE; pcvr->pbValue = (BYTE *) &NameIdMin; pcvr->cbValue = sizeof(NameIdMin); pcvr++;
// NameId <= MAKECANAMEID(iCert == _16BITMASK, iKey)
NameIdMax = MAKECANAMEID(_16BITMASK, iKey); pcvr->ColumnIndex = DTI_CERTIFICATETABLE | DTC_CERTIFICATEISSUERNAMEID; pcvr->SeekOperator = CVR_SEEK_LE; pcvr->SortOrder = CVR_SORT_NONE; pcvr->pbValue = (BYTE *) &NameIdMax; pcvr->cbValue = sizeof(NameIdMax); pcvr++;
CSASSERT(ARRAYSIZE(acvr) > SAFE_SUBTRACT_POINTERS(pcvr, acvr));
if (NULL != pftQueryMinimum) { // Request.RevokedWhen >= *pftQueryMinimum
pcvr->ColumnIndex = DTI_REQUESTTABLE | DTR_REQUESTREVOKEDWHEN; pcvr->SeekOperator = CVR_SEEK_GE; pcvr->SortOrder = CVR_SORT_NONE; pcvr->pbValue = (BYTE *) pftQueryMinimum; pcvr->cbValue = sizeof(*pftQueryMinimum); pcvr++;
CSASSERT(ARRAYSIZE(acvr) >= SAFE_SUBTRACT_POINTERS(pcvr, acvr)); }
hr = g_pCertDB->OpenView( SAFE_SUBTRACT_POINTERS(pcvr, acvr), acvr, ARRAYSIZE(g_aColCRL), g_aColCRL, 0, // no worker thread
&pView); _JumpIfError(hr, error, "OpenView");
fEnd = FALSE; while (!fEnd) { hr = pView->Next(ARRAYSIZE(aResult), aResult, &celtFetched); if (S_FALSE == hr) { fEnd = TRUE; if (0 == celtFetched) { break; } hr = S_OK; } _JumpIfError(hr, error, "Next");
fResultActive = TRUE;
CSASSERT(ARRAYSIZE(aResult) >= celtFetched);
for (i = 0; i < celtFetched; i++) { DWORD Disposition; DWORD Reason; CERTDBRESULTROW *pResult = &aResult[i];
CSASSERT(ARRAYSIZE(g_aColCRL) == pResult->ccol);
CSASSERT(NULL != pResult->acol[ICOL_DISPOSITION].pbValue); CSASSERT(PROPTYPE_LONG == (PROPTYPE_MASK & pResult->acol[ICOL_DISPOSITION].Type)); CSASSERT(sizeof(Disposition) == pResult->acol[ICOL_DISPOSITION].cbValue); Disposition = *(DWORD *) pResult->acol[ICOL_DISPOSITION].pbValue;
CSASSERT(NULL != pResult->acol[ICOL_SERIAL].pbValue); CSASSERT(PROPTYPE_STRING == (PROPTYPE_MASK & pResult->acol[ICOL_SERIAL].Type)); CSASSERT(0 < pResult->acol[ICOL_SERIAL].cbValue);
if (NULL == pResult->acol[ICOL_EFFECTIVEWHEN].pbValue) { continue; } CSASSERT(sizeof(FILETIME) == pResult->acol[ICOL_EFFECTIVEWHEN].cbValue); CSASSERT(PROPTYPE_DATE == (PROPTYPE_MASK & pResult->acol[ICOL_EFFECTIVEWHEN].Type));
CSASSERT(PROPTYPE_LONG == (PROPTYPE_MASK & pResult->acol[ICOL_REASON].Type)); Reason = CRL_REASON_UNSPECIFIED; if (NULL != pResult->acol[ICOL_REASON].pbValue) { CSASSERT(sizeof(Reason) == pResult->acol[ICOL_REASON].cbValue); Reason = *(DWORD *) pResult->acol[ICOL_REASON].pbValue; }
if (NULL == pResult->acol[ICOL_SERIAL].pbValue || CRL_REASON_REMOVE_FROM_CRL == Reason) { continue; }
// Add to CRL unless it's:
// not a revoked issued cert &&
// not a root CA cert &&
// not an unrevoked issued cert
if (DB_DISP_REVOKED != Disposition && !(DB_DISP_CA_CERT == Disposition && IsRootCA(g_CAType)) && !(DB_DISP_ISSUED == Disposition && MAXDWORD == Reason)) { continue; } if (MAXDWORD == Reason) { if (!fDelta) { continue; } Reason = CRL_REASON_REMOVE_FROM_CRL; } hr = AddCRLElement( &pBlockSerial, &pBlockCRL, (WCHAR const *) pResult->acol[ICOL_SERIAL].pbValue, (FILETIME const *) pResult->acol[ICOL_EFFECTIVEWHEN].pbValue, Reason); _JumpIfError(hr, error, "AddCRLElement");
CONSOLEPRINT3(( DBG_SS_CERTSRV, "Cert is %ws: %ws: %d\n", CRL_REASON_REMOVE_FROM_CRL == Reason? L"UNREVOKED" : L"Revoked", pResult->acol[ICOL_SERIAL].pbValue, Reason)); cCRL++; } pView->ReleaseResultRow(celtFetched, aResult); fResultActive = FALSE; } *pcCRL = cCRL; hr = S_OK;
error: *ppBlockSerial = pBlockSerial; *ppBlockCRL = pBlockCRL; if (NULL != pView) { if (fResultActive) { pView->ReleaseResultRow(celtFetched, aResult); } pView->Release(); } return(hr); } #undef ICOL_DISPOSITION
#undef ICOL_SERIAL
#undef ICOL_EFFECTIVEWHEN
#undef ICOL_REASON
HRESULT crlBuildCRLArray( IN BOOL fDelta, OPTIONAL IN FILETIME const *pftQueryMinimum, IN FILETIME const *pftThisPublish, IN FILETIME const *pftLastPublishBase, IN DWORD iKey, OUT DWORD *pcCRL, OUT CRL_ENTRY **paCRL, OUT VOID **ppvBlock) { HRESULT hr; BOOL fCoInitialized = FALSE; CSCRLBLOCK *pBlockCRL = NULL; CSMEMBLOCK *pBlockSerial = NULL;
*pcCRL = 0; *paCRL = NULL; *ppvBlock = NULL;
hr = CoInitializeEx(NULL, GetCertsrvComThreadingModel()); if (S_OK != hr && S_FALSE != hr) { _JumpError(hr, error, "CoInitializeEx"); } fCoInitialized = TRUE;
hr = BuildCRLList( fDelta, iKey, pftQueryMinimum, pftThisPublish, pftLastPublishBase, pcCRL, &pBlockCRL, &pBlockSerial); _JumpIfError(hr, error, "BuildCRLList");
hr = ConvertOrFreeCRLList(&pBlockCRL, &pBlockSerial, *pcCRL, paCRL); _JumpIfError(hr, error, "ConvertOrFreeCRLList");
*ppvBlock = pBlockSerial; pBlockSerial = NULL;
error: if (NULL != pBlockCRL) { ConvertOrFreeCRLList(&pBlockCRL, NULL, 0, NULL); } if (NULL != pBlockSerial) { crlBlockListFree(pBlockSerial); } if (fCoInitialized) { CoUninitialize(); } return(hr); }
HRESULT crlGetRegCRLNextPublish( IN BOOL fDelta, IN WCHAR const *pwszSanitizedName, IN WCHAR const *pwszRegName, OUT FILETIME *pftNextPublish) { HRESULT hr; BYTE *pbData = NULL; DWORD cbData; DWORD dwType;
hr = myGetCertRegValue( NULL, pwszSanitizedName, NULL, NULL, pwszRegName, &pbData, // free using LocalFree
&cbData, &dwType); if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) { hr = S_OK; goto error; } _JumpIfErrorStr(hr, error, "myGetCertRegValue", pwszRegName);
if (REG_BINARY != dwType || sizeof(*pftNextPublish) != cbData) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); goto error; } *pftNextPublish = *(FILETIME *) pbData; DBGPRINTTIME(&fDelta, "*pftNextPublish", DPT_DATE, *pftNextPublish); error: if (NULL != pbData) { LocalFree(pbData); } return(hr); }
HRESULT crlSetRegCRLNextPublish( IN BOOL fDelta, IN WCHAR const *pwszSanitizedName, IN WCHAR const *pwszRegName, IN FILETIME const *pftNextPublish) { HRESULT hr;
hr = mySetCertRegValue( NULL, pwszSanitizedName, NULL, NULL, pwszRegName, REG_BINARY, (BYTE const *) pftNextPublish, sizeof(*pftNextPublish), FALSE); _JumpIfErrorStr(hr, error, "mySetCertRegValue", pwszRegName);
DBGPRINTTIME(&fDelta, "*pftNextPublish", DPT_DATE, *pftNextPublish);
error: return(hr); }
// called from CoreInit
// inits process-static data: g_ftCRLNextPublish, etc.
HRESULT CRLInit( IN WCHAR const *pwszSanitizedName) { HRESULT hr; DWORD dw;
ZeroMemory(&g_ftCRLNextPublish, sizeof(g_ftCRLNextPublish)); ZeroMemory(&g_ftDeltaCRLNextPublish, sizeof(g_ftDeltaCRLNextPublish));
hr = crlGetRegCRLNextPublish( FALSE, pwszSanitizedName, wszREGCRLNEXTPUBLISH, &g_ftCRLNextPublish); _JumpIfError(hr, error, "crlGetRegCRLNextPublish");
hr = crlGetRegCRLNextPublish( TRUE, pwszSanitizedName, wszREGCRLDELTANEXTPUBLISH, &g_ftDeltaCRLNextPublish); _JumpIfError(hr, error, "crlGetRegCRLNextPublish");
hr = myGetCertRegDWValue( pwszSanitizedName, NULL, NULL, wszREGCRLFLAGS, (DWORD *) &dw); _PrintIfErrorStr(hr, "myGetCertRegDWValue", wszREGCRLFLAGS); if (S_OK == hr) { g_dwCRLFlags = dw; } hr = S_OK;
error: return(hr); }
VOID CRLTerminate() { if (NULL != g_pld) { ldap_unbind(g_pld); g_pld = NULL; } }
HRESULT crlGetRegPublishParams( IN BOOL fDelta, IN WCHAR const *pwszSanitizedName, IN WCHAR const *pwszRegCRLPeriodCount, IN WCHAR const *pwszRegCRLPeriodString, IN WCHAR const *pwszRegCRLOverlapPeriodCount, IN WCHAR const *pwszRegCRLOverlapPeriodString, IN LONG lPeriodCountDefault, IN WCHAR const *pwszPeriodStringDefault, OPTIONAL OUT CSCRLPERIOD *pccp, OUT BOOL *pfCRLPublishDisabled) { HRESULT hr; WCHAR *pwszCRLPeriodString = NULL; WCHAR *pwszCRLOverlapPeriodString = NULL; DWORD cbData; DWORD dwPeriod; DWORD dwType; CSCRLPERIOD ccp;
if (NULL == pccp) { pccp = &ccp; } ZeroMemory(pccp, sizeof(*pccp));
CSASSERT(NULL != pfCRLPublishDisabled);
// get if need lCRLPeriodCount OR enumCRLPeriod
// if any of these fail, skip to error handling below
hr = myGetCertRegDWValue( pwszSanitizedName, NULL, NULL, pwszRegCRLPeriodCount, (DWORD *) &pccp->lCRLPeriodCount); _PrintIfErrorStr(hr, "myGetCertRegDWValue", pwszRegCRLPeriodCount);
if (hr == S_OK) { hr = myGetCertRegStrValue( pwszSanitizedName, NULL, NULL, pwszRegCRLPeriodString, &pwszCRLPeriodString); _PrintIfErrorStr(hr, "myGetCertRegDWValue", pwszRegCRLPeriodString); if (hr == S_OK) { hr = myTranslatePeriodUnits( pwszCRLPeriodString, pccp->lCRLPeriodCount, &pccp->enumCRLPeriod, &pccp->lCRLPeriodCount); _PrintIfError(hr, "myTranslatePeriodUnits"); } // don't allow base to be disabled anymore: force defaults to be loaded
if (!fDelta && (0 == pccp->lCRLPeriodCount || -1 == pccp->lCRLPeriodCount)) { hr = E_INVALIDARG; } }
if (hr != S_OK) { _PrintError(hr, "Error reading CRLPub params. Overwriting with defaults.");
if (CERTLOG_WARNING <= g_dwLogLevel) { hr = LogEvent( EVENTLOG_WARNING_TYPE, MSG_INVALID_CRL_SETTINGS, 0, NULL); _PrintIfError(hr, "LogEvent"); }
// slam default publishing to whatever the caller said
hr = myTranslatePeriodUnits( pwszPeriodStringDefault, lPeriodCountDefault, &pccp->enumCRLPeriod, &pccp->lCRLPeriodCount); _JumpIfError(hr, error, "myTranslatePeriodUnits");
// blindly reset defaults
mySetCertRegDWValue( pwszSanitizedName, NULL, NULL, pwszRegCRLPeriodCount, pccp->lCRLPeriodCount);
mySetCertRegStrValue( pwszSanitizedName, NULL, NULL, pwszRegCRLPeriodString, pwszPeriodStringDefault); } *pfCRLPublishDisabled = 0 == pccp->lCRLPeriodCount;
if (&ccp != pccp) // If caller wants the data
{ BOOL fRegistryOverlap = FALSE; DWORD dwCRLOverlapCount; ENUM_PERIOD enumCRLOverlap; LLFILETIME llftDeltaPeriod;
// try and gather overlap values from registry - bail on any failure
hr = myGetCertRegDWValue( pwszSanitizedName, NULL, NULL, pwszRegCRLOverlapPeriodCount, &dwCRLOverlapCount); if (hr == S_OK && 0 != dwCRLOverlapCount) // if not disabled
{ hr = myGetCertRegStrValue( pwszSanitizedName, NULL, NULL, pwszRegCRLOverlapPeriodString, &pwszCRLOverlapPeriodString);// free w/ LocalFree
if (hr == S_OK) { hr = myTranslatePeriodUnits( pwszCRLOverlapPeriodString, dwCRLOverlapCount, &enumCRLOverlap, (LONG *) &dwCRLOverlapCount);
// we have enough info to override overlap calculation
if (hr == S_OK) { fRegistryOverlap = TRUE; DBGPRINT(( DBG_SS_CERTSRVI, "Loaded CRL Overlap values. Overriding overlap calculation with specified values.\n")); } } }
// always possible to revert to calculated value
if (fRegistryOverlap) { LLFILETIME llftOverlap;
// convert registry-specified CRL overlap to FILETIME
llftOverlap.ll = 0; myMakeExprDateTime( &llftOverlap.ft, dwCRLOverlapCount, enumCRLOverlap); DBGPRINTTIME(&fDelta, "ftdelta1", DPT_DELTA, llftOverlap.ft);
llftOverlap.ll /= CVT_BASE; // now in seconds
// (DELTA sec / 60 secpermin)
pccp->dwCRLOverlapMinutes = (DWORD) (llftOverlap.ll / CVT_MINUTES); }
// convert CRL period to FILETIME
llftDeltaPeriod.ll = 0; myMakeExprDateTime( &llftDeltaPeriod.ft, pccp->lCRLPeriodCount, pccp->enumCRLPeriod); DBGPRINTTIME(&fDelta, "ftdelta2", DPT_DELTA, llftDeltaPeriod.ft);
llftDeltaPeriod.ll /= CVT_BASE; // now in seconds
llftDeltaPeriod.ll /= CVT_MINUTES; // now in minutes
if (!fRegistryOverlap) { if (fDelta) { // default CRLOverlap for delta CRLs: same as period
pccp->dwCRLOverlapMinutes = llftDeltaPeriod.ft.dwLowDateTime; } else { // default CRLOverlap for base CRLs: 10% of period
pccp->dwCRLOverlapMinutes = (DWORD) (llftDeltaPeriod.ll / 10); }
// Clamp computed overlap to less than 12 hours
if (pccp->dwCRLOverlapMinutes > 12 * 60) { pccp->dwCRLOverlapMinutes = 12 * 60; } }
// Always clamp lower bound: (1.5 * skew) < g_dwCRLOverlapMinutes
// must be at least 1.5x skew
dwCRLOverlapCount = (3 * g_dwClockSkewMinutes) >> 1; if (pccp->dwCRLOverlapMinutes < dwCRLOverlapCount) { pccp->dwCRLOverlapMinutes = dwCRLOverlapCount; }
// Always clamp upper bound: must be no more than CRL period
if (pccp->dwCRLOverlapMinutes > llftDeltaPeriod.ft.dwLowDateTime) { pccp->dwCRLOverlapMinutes = llftDeltaPeriod.ft.dwLowDateTime; } } hr = S_OK;
error: if (NULL != pwszCRLPeriodString) { LocalFree(pwszCRLPeriodString); } if (NULL != pwszCRLOverlapPeriodString) { LocalFree(pwszCRLOverlapPeriodString); } return(hr); }
// Reload publication params during each CRL publication
HRESULT crlGetRegCRLPublishParams( IN WCHAR const *pwszSanitizedName, OPTIONAL OUT CSCRLPERIOD *pccpBase, OPTIONAL OUT CSCRLPERIOD *pccpDelta) { HRESULT hr;
hr = crlGetRegPublishParams( FALSE, pwszSanitizedName, wszREGCRLPERIODCOUNT, wszREGCRLPERIODSTRING, wszREGCRLOVERLAPPERIODCOUNT, wszREGCRLOVERLAPPERIODSTRING, dwCRLPERIODCOUNTDEFAULT, // default period
wszCRLPERIODSTRINGDEFAULT, // default period
pccpBase, &g_fCRLPublishDisabled); _JumpIfError(hr, error, "crlGetRegPublishParams");
hr = crlGetRegPublishParams( TRUE, pwszSanitizedName, wszREGCRLDELTAPERIODCOUNT, wszREGCRLDELTAPERIODSTRING, wszREGCRLDELTAOVERLAPPERIODCOUNT, wszREGCRLDELTAOVERLAPPERIODSTRING, dwCRLDELTAPERIODCOUNTDEFAULT, // default period
wszCRLDELTAPERIODSTRINGDEFAULT, // default period
pccpDelta, &g_fDeltaCRLPublishDisabled); _JumpIfError(hr, error, "crlGetRegPublishParams");
error: return(hr); }
#define CERTSRV_CRLPUB_RETRY_COUNT_DEFAULT 10
#define CERTSRV_CRLPUB_RETRY_SECONDS (10 * CVT_MINUTES)
VOID crlComputeTimeOutSub( OPTIONAL IN BOOL *pfDelta, IN FILETIME const *pftFirst, IN FILETIME const *pftLast, OUT DWORD *pdwMSTimeOut) { LLFILETIME llft;
// llft.ll = *pftLast - *pftFirst;
llft.ll = mySubtractFileTimes(pftLast, pftFirst); DBGPRINTTIME(pfDelta, "*pftFirst", DPT_DATE, *pftFirst); DBGPRINTTIME(pfDelta, "*pftLast", DPT_DATE, *pftLast);
llft.ll /= (CVT_BASE / 1000); // convert 100ns to msecs
DBGPRINTTIME(pfDelta, "llft", DPT_DELTAMS, llft.ft);
if (0 > llft.ll || MAXLONG < llft.ll) { // wait as long as we can without going infinite
llft.ll = MAXLONG; } *pdwMSTimeOut = llft.ft.dwLowDateTime; }
VOID crlComputeTimeOutEx( IN BOOL fDelta, IN FILETIME const *pftFirst, IN FILETIME const *pftLast, OUT DWORD *pdwMSTimeOut) { crlComputeTimeOutSub(&fDelta, pftFirst, pftLast, pdwMSTimeOut); }
VOID CRLComputeTimeOut( IN FILETIME const *pftFirst, IN FILETIME const *pftLast, OUT DWORD *pdwMSTimeOut) { crlComputeTimeOutSub(NULL, pftFirst, pftLast, pdwMSTimeOut); }
#ifdef DBG_CERTSRV_DEBUG_PRINT
VOID DbgPrintRemainTime( IN BOOL fDelta, IN FILETIME const *pftCurrent, IN FILETIME const *pftCRLNextPublish) { HRESULT hr; LLFILETIME llftDelta; WCHAR *pwszTime = NULL; WCHAR awc[1];
llftDelta.ll = mySubtractFileTimes(pftCRLNextPublish, pftCurrent);
DBGPRINTTIME(&fDelta, "delta", DPT_DELTA, llftDelta.ft);
llftDelta.ll = -llftDelta.ll; hr = myFileTimePeriodToWszTimePeriod( &llftDelta.ft, TRUE, // fExact
&pwszTime); _PrintIfError(hr, "myFileTimePeriodToWszTimePeriod"); if (S_OK != hr) { awc[0] = L'\0'; pwszTime = awc; }
DBGPRINT(( DBG_SS_CERTSRV, "CRLPubWakeupEvent(tid=%d): Next %hs CRL: %ws\n", GetCurrentThreadId(), fDelta? "Delta" : "Base", pwszTime)); if (NULL != pwszTime && awc != pwszTime) { LocalFree(pwszTime); } } #endif // DBG_CERTSRV_DEBUG_PRINT
DWORD g_aColExpiredCRL[] = {
#define ICOLEXP_ROWID 0
DTI_CRLTABLE | DTL_ROWID,
#define ICOLEXP_MINBASE 1
DTI_CRLTABLE | DTL_MINBASE,
#define ICOLEXP_CRLNEXTUPDATE 2
DTI_CRLTABLE | DTL_NEXTUPDATEDATE, };
HRESULT crlDeleteExpiredCRLs( IN FILETIME const *pftCurrent, IN FILETIME const *pftQueryDeltaDelete, IN DWORD RowIdBase) { HRESULT hr; CERTVIEWRESTRICTION acvr[1]; CERTVIEWRESTRICTION *pcvr; IEnumCERTDBRESULTROW *pView = NULL; BOOL fResultActive = FALSE; CERTDBRESULTROW aResult[1]; CERTDBRESULTROW *pResult; DWORD celtFetched;
if (CRLF_DELETE_EXPIRED_CRLS & g_dwCRLFlags) { DBGPRINTTIME(NULL, "DeleteCRL:*pftCurrent", DPT_DATE, *pftCurrent); DBGPRINTTIME(NULL, "DeleteCRL:*pftQueryDeltaDelete", DPT_DATE, *pftQueryDeltaDelete);
// Set up restrictions as follows:
pcvr = acvr;
// CRL Expiration < ftCurrent (indexed column)
pcvr->ColumnIndex = DTI_CRLTABLE | DTL_NEXTPUBLISHDATE; pcvr->SeekOperator = CVR_SEEK_LT; pcvr->SortOrder = CVR_SORT_ASCEND; // Oldest propagated CRL first
pcvr->pbValue = (BYTE *) pftCurrent; pcvr->cbValue = sizeof(*pftCurrent); pcvr++;
CSASSERT(ARRAYSIZE(acvr) == SAFE_SUBTRACT_POINTERS(pcvr, acvr));
hr = g_pCertDB->OpenView( ARRAYSIZE(acvr), acvr, ARRAYSIZE(g_aColExpiredCRL), g_aColExpiredCRL, 0, // no worker thread
&pView); _JumpIfError(hr, error, "OpenView");
while (TRUE) { DWORD RowId; DWORD MinBase; FILETIME ftNextUpdate; BOOL fDelete; hr = pView->Next(ARRAYSIZE(aResult), aResult, &celtFetched); if (S_FALSE == hr) { if (0 == celtFetched) { break; } } _JumpIfError(hr, error, "Next");
fResultActive = TRUE;
CSASSERT(ARRAYSIZE(aResult) == celtFetched);
pResult = &aResult[0];
CSASSERT(ARRAYSIZE(g_aColExpiredCRL) == pResult->ccol); CSASSERT(NULL != pResult->acol[ICOLEXP_ROWID].pbValue); CSASSERT(PROPTYPE_LONG == (PROPTYPE_MASK & pResult->acol[ICOLEXP_ROWID].Type)); CSASSERT(sizeof(RowId) == pResult->acol[ICOLEXP_ROWID].cbValue); RowId = *(DWORD *) pResult->acol[ICOLEXP_ROWID].pbValue;
CSASSERT(NULL != pResult->acol[ICOLEXP_MINBASE].pbValue); CSASSERT(PROPTYPE_LONG == (PROPTYPE_MASK & pResult->acol[ICOLEXP_MINBASE].Type)); CSASSERT(sizeof(MinBase) == pResult->acol[ICOLEXP_MINBASE].cbValue); MinBase = *(DWORD *) pResult->acol[ICOLEXP_MINBASE].pbValue;
CSASSERT(NULL != pResult->acol[ICOLEXP_CRLNEXTUPDATE].pbValue); CSASSERT(PROPTYPE_DATE == (PROPTYPE_MASK & pResult->acol[ICOLEXP_CRLNEXTUPDATE].Type)); CSASSERT(sizeof(FILETIME) == pResult->acol[ICOLEXP_CRLNEXTUPDATE].cbValue); ftNextUpdate = *(FILETIME *) pResult->acol[ICOLEXP_CRLNEXTUPDATE].pbValue;
pView->ReleaseResultRow(celtFetched, aResult); fResultActive = FALSE;
CSASSERT(0 != RowId);
// Delete the CRL row if it is not the current Base CRL and the
// row represents a CRL that expired prior to the current Base CRL.
fDelete = FALSE; if (RowIdBase != RowId && 0 < CompareFileTime(pftQueryDeltaDelete, &ftNextUpdate)) { fDelete = TRUE; }
DBGPRINTTIME(NULL, "DeleteCRL:ftNextUpdate", DPT_DATE, ftNextUpdate); DBGPRINT(( DBG_SS_CERTSRVI, "crlDeleteExpiredCRLs(RowId=%x) %ws\n", RowId, fDelete? L"DELETE" : L"SKIP"));
if (fDelete) { ICertDBRow *prow;
hr = g_pCertDB->OpenRow( PROPOPEN_DELETE | PROPTABLE_CRL, RowId, NULL, &prow); _JumpIfError(hr, error, "OpenRow");
hr = prow->Delete(); _PrintIfError(hr, "Delete");
if (S_OK == hr) { hr = prow->CommitTransaction(TRUE); _PrintIfError(hr, "CommitTransaction"); } if (S_OK != hr) { HRESULT hr2 = prow->CommitTransaction(FALSE); _PrintIfError(hr2, "CommitTransaction"); } prow->Release(); } } } hr = S_OK;
error: if (NULL != pView) { if (fResultActive) { pView->ReleaseResultRow(celtFetched, aResult); } pView->Release(); } return(hr); } #undef ICOLEXP_ROWID
#undef ICOLEXP_MINBASE
#undef ICOLEXP_CRLNEXTUPDATE
///////////////////////////////////////////////////
// CRLPubWakeupEvent is the handler for wakeup notifications.
//
// This function is called at miscellaneous times and
// determines whether or not it is time to rebuild the
// CRL to be published.
//
// It then calls CRLPublishCRLs and advises it as to whether to
// rebuild or not.
//
// Its final task is to recalculate the next wakeup time, which
// depends on current time, if the exit module needs to be retried,
// or whether CRL publishing is disabled.
HRESULT CRLPubWakeupEvent( OUT DWORD *pdwMSTimeOut) { HRESULT hr; HRESULT hrPublish; FILETIME ftZero; FILETIME ftCurrent; BOOL fBaseTrigger = TRUE; BOOL fRebuildCRL = FALSE; BOOL fForceRepublish = FALSE; BOOL fShadowDelta = FALSE; BOOL fSetRetryTimer = FALSE; DWORD dwMSTimeOut = CERTSRV_CRLPUB_RETRY_SECONDS * 1000; DWORD State = 0; static BOOL s_fFirstWakeup = TRUE;
CSASSERT(NULL != pdwMSTimeOut);
// if anything goes wrong, call us again after a pause
hr = CertSrvEnterServer(&State); _JumpIfError(hr, error, "CertSrvEnterServer");
__try { BOOL fCRLPublishDisabledOld = g_fCRLPublishDisabled; BOOL fDeltaCRLPublishDisabledOld = g_fDeltaCRLPublishDisabled;
// Recalc Timeout
GetSystemTimeAsFileTime(&ftCurrent);
#ifdef DBG_CERTSRV_DEBUG_PRINT
{ WCHAR *pwszNow = NULL;
myGMTFileTimeToWszLocalTime(&ftCurrent, TRUE, &pwszNow);
DBGPRINT((DBG_SS_CERTSRV, "CRLPubWakeupEvent(%ws)\n", pwszNow));
if (NULL != pwszNow) { LocalFree(pwszNow); } } #endif // DBG_CERTSRV_DEBUG_PRINT
// get current publish params
hr = crlGetRegCRLPublishParams(g_wszSanitizedName, NULL, NULL); _LeaveIfError(hr, "crlGetRegCRLPublishParams");
if (s_fFirstWakeup) { s_fFirstWakeup = FALSE; if (g_fDBRecovered) { fForceRepublish = TRUE; } } else { if (!g_fCRLPublishDisabled && (fCRLPublishDisabledOld || g_fDeltaCRLPublishDisabled != fDeltaCRLPublishDisabledOld)) { fRebuildCRL = TRUE; // state change: force new CRLs
// If delta CRLs were just now disabled, make one attempt to
// publish shadow deltas; force clients to fetch a new base CRL.
if (!fDeltaCRLPublishDisabledOld && g_fDeltaCRLPublishDisabled) { fShadowDelta = TRUE; // force shadow delta
} } }
// if "not yet ready"
if (0 < CompareFileTime(&g_ftCRLNextPublish, &ftCurrent)) { fBaseTrigger = FALSE; #ifdef DBG_CERTSRV_DEBUG_PRINT
// give next pub status
DbgPrintRemainTime(FALSE, &ftCurrent, &g_ftCRLNextPublish); #endif // DBG_CERTSRV_DEBUG_PRINT
}
// if "not yet ready"
if (!fBaseTrigger && (g_fDeltaCRLPublishDisabled || 0 < CompareFileTime(&g_ftDeltaCRLNextPublish, &ftCurrent))) { #ifdef DBG_CERTSRV_DEBUG_PRINT
// give next pub status
if (!g_fDeltaCRLPublishDisabled) { DbgPrintRemainTime(TRUE, &ftCurrent, &g_ftDeltaCRLNextPublish); } #endif // DBG_CERTSRV_DEBUG_PRINT
} else // "ready to publish" trigger
{ if (!g_fCRLPublishDisabled) // is publishing enabled?
{ fRebuildCRL = TRUE; // ENABLED, ready to go!
} else { DBGPRINT(( DBG_SS_CERTSRV, "CRLPubWakeupEvent(tid=%d): Publishing disabled\n", GetCurrentThreadId() )); } }
ftZero.dwLowDateTime = 0; ftZero.dwHighDateTime = 0;
while (TRUE) { hr = CRLPublishCRLs( fRebuildCRL, fForceRepublish, NULL, // pwszUserName
!fForceRepublish && // fDeltaOnly
!fBaseTrigger && !g_fDeltaCRLPublishDisabled && !fDeltaCRLPublishDisabledOld, fShadowDelta, ftZero, &fSetRetryTimer, &hrPublish); if (S_OK == hr) { break; } _PrintError(hr, "CRLPublishCRLs");
if (!fForceRepublish || fRebuildCRL) { _leave; // give up
}
// We failed to republish existing CRLs after a database restore
// and recovery; generate new base and delta CRLs and publish them.
fRebuildCRL = TRUE; } _PrintIfError(hrPublish, "CRLPublishCRLs(hrPublish)");
// if we called CRLPublishCRLs, clear the manual event it'll trigger
ResetEvent(g_hCRLManualPublishEvent);
// how many ms until next publish? set dwMSTimeOut
if (g_fCRLPublishDisabled) { // if disabled, don't set timeout
dwMSTimeOut = INFINITE; CONSOLEPRINT1(( DBG_SS_CERTSRV, "CRL Publishing Disabled, TimeOut=INFINITE (%d ms)\n", dwMSTimeOut)); } else { DWORD dwMSTimeOutDelta; WCHAR *pwszCRLType = NULL;
crlComputeTimeOutEx( FALSE, &ftCurrent, &g_ftCRLNextPublish, &dwMSTimeOut);
if (g_fDeltaCRLPublishDisabled) { pwszCRLType = L"Base"; } else { crlComputeTimeOutEx( TRUE, &ftCurrent, &g_ftDeltaCRLNextPublish, &dwMSTimeOutDelta); if (dwMSTimeOut > dwMSTimeOutDelta) { dwMSTimeOut = dwMSTimeOutDelta; } pwszCRLType = L"Base + Delta"; } if (NULL != pwszCRLType) { LONGLONG ll; WCHAR *pwszTimePeriod = NULL; WCHAR awc[1];
ll = dwMSTimeOut; ll *= CVT_BASE / 1000; // milliseconds to FILETIME Period
ll = -ll; // FILETIME Period must be negative
hr = myFileTimePeriodToWszTimePeriod( (FILETIME const *) &ll, TRUE, // fExact
&pwszTimePeriod); _PrintIfError(hr, "myFileTimePeriodToWszTimePeriod"); if (S_OK != hr) { awc[0] = L'\0'; pwszTimePeriod = awc; } CONSOLEPRINT3(( DBG_SS_CERTSRV, "%ws CRL Publishing Enabled, TimeOut=%ds, %ws\n", pwszCRLType, dwMSTimeOut/1000, pwszTimePeriod)); if (NULL != pwszTimePeriod && awc != pwszTimePeriod) { LocalFree(pwszTimePeriod); } } }
// if we need to retry, wait no longer than the retry period
if (fSetRetryTimer) { if (dwMSTimeOut > CERTSRV_CRLPUB_RETRY_SECONDS * 1000) { dwMSTimeOut = CERTSRV_CRLPUB_RETRY_SECONDS * 1000; CONSOLEPRINT1(( DBG_SS_CERTSRV, "CRL Publishing periodic retry, TimeOut=%ds\n", dwMSTimeOut/1000)); } } hr = S_OK; } __except(hr = myHEXCEPTIONCODE(), EXCEPTION_EXECUTE_HANDLER) { _PrintError(hr, "Exception"); }
error: *pdwMSTimeOut = dwMSTimeOut; CertSrvExitServer(State); return(hr); }
HRESULT WriteToLockedFile( IN BYTE const *pbEncoded, IN DWORD cbEncoded, IN LPCWSTR szFileDir, IN LPCWSTR szFile) { HRESULT hr; WCHAR wszTmpPrepFile[MAX_PATH]; WCHAR wszTmpInUseFile[MAX_PATH]; BYTE *pbData = NULL; DWORD cbData;
// According to JohnL, the best way to do this is to gen a temp
// file name, rename the existing file to that, then delete it.
//
// Logic:
// create unique preparation filename
// write new data to prep file
// create unique destination filename for old file (possibly locked)
// move old file to destination filename
// move prep file to (vacated) file name
// delete old file from destination filename
hr = DecodeFileW(szFile, &pbData, &cbData, CRYPT_STRING_BINARY); if (S_OK == hr && cbEncoded == cbData && 0 == memcmp(pbData, pbEncoded, cbData)) { CSASSERT(S_OK == hr); goto error; // already written, do nothing
}
// create a prep file
if (0 == GetTempFileName(szFileDir, L"pre", 0, wszTmpPrepFile)) { hr = myHLastError(); _JumpError(hr, error, "GetTempFileName"); }
// write file to prep area
hr = EncodeToFileW( wszTmpPrepFile, pbEncoded, cbEncoded, DECF_FORCEOVERWRITE | CRYPT_STRING_BINARY); _JumpIfError(hr, error, "EncodeToFileW");
if (0 == GetTempFileName(szFileDir, L"crl", 0, wszTmpInUseFile)) { hr = myHLastError(); _JumpError(hr, error, "GetTempFileName"); }
// move old to "in use" file (empty file already exists from
// GetTempFileName call) may not exist, so don't bother checking status
MoveFileEx( szFile, wszTmpInUseFile, MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING);
// move prepared file to current file
if (!MoveFileEx(wszTmpPrepFile, szFile, MOVEFILE_WRITE_THROUGH)) { hr = myHLastError(); _JumpError(hr, error, "MoveFileEx"); }
// The "in use" file may not exist, so don't bother checking status.
DeleteFile(wszTmpInUseFile); hr = S_OK;
error: if (NULL != pbData) { LocalFree(pbData); } return(hr); }
WCHAR const g_wszPropCRLNumber[] = wszPROPCRLNUMBER; WCHAR const g_wszPropCRLMinBase[] = wszPROPCRLMINBASE; WCHAR const g_wszPropCRLNameId[] = wszPROPCRLNAMEID; WCHAR const g_wszPropCRLCount[] = wszPROPCRLCOUNT; WCHAR const g_wszPropCRLThisUpdateDate[] = wszPROPCRLTHISUPDATE; WCHAR const g_wszPropCRLNextUpdateDate[] = wszPROPCRLNEXTUPDATE; WCHAR const g_wszPropCRLThisPublishDate[] = wszPROPCRLTHISPUBLISH; WCHAR const g_wszPropCRLNextPublishDate[] = wszPROPCRLNEXTPUBLISH; WCHAR const g_wszPropCRLEffectiveDate[] = wszPROPCRLEFFECTIVE; WCHAR const g_wszPropCRLPropagationCompleteDate[] = wszPROPCRLPROPAGATIONCOMPLETE; WCHAR const g_wszPropCRLLastPublished[] = wszPROPCRLLASTPUBLISHED; WCHAR const g_wszPropCRLPublishAttempts[] = wszPROPCRLPUBLISHATTEMPTS; WCHAR const g_wszPropCRLPublishFlags[] = wszPROPCRLPUBLISHFLAGS; WCHAR const g_wszPropCRLPublishStatusCode[] = wszPROPCRLPUBLISHSTATUSCODE; WCHAR const g_wszPropCRLPublishError[] = wszPROPCRLPUBLISHERROR; WCHAR const g_wszPropCRLRawCRL[] = wszPROPCRLRAWCRL;
HRESULT crlWriteCRLToDB( IN DWORD CRLNumber, IN DWORD CRLMinBase, // 0 implies base CRL
OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
IN BOOL fShadowDelta, // empty delta CRL with new MinBaseCRL
IN DWORD CRLNameId, IN DWORD CRLCount, IN FILETIME const *pftThisUpdate, IN FILETIME const *pftNextUpdate, IN FILETIME const *pftThisPublish, IN FILETIME const *pftNextPublish, OPTIONAL IN FILETIME const *pftQuery, IN FILETIME const *pftPropagationComplete, OPTIONAL IN BYTE const *pbCRL, IN DWORD cbCRL, OUT DWORD *pdwRowId) { HRESULT hr; ICertDBRow *prow = NULL; DWORD CRLPublishFlags; BOOL fCommitted = FALSE;
*pdwRowId = 0;
// Create a new CRL table entry
hr = g_pCertDB->OpenRow( PROPTABLE_CRL, 0, NULL, &prow); _JumpIfError(hr, error, "OpenRow");
prow->GetRowId(pdwRowId);
hr = prow->SetProperty( g_wszPropCRLNumber, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(CRLNumber), (BYTE const *) &CRLNumber); _JumpIfError(hr, error, "SetProperty");
hr = prow->SetProperty( g_wszPropCRLMinBase, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(CRLMinBase), (BYTE const *) &CRLMinBase); _JumpIfError(hr, error, "SetProperty");
hr = prow->SetProperty( g_wszPropCRLNameId, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(CRLNameId), (BYTE const *) &CRLNameId); _JumpIfError(hr, error, "SetProperty");
hr = prow->SetProperty( g_wszPropCRLCount, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(CRLCount), (BYTE const *) &CRLCount); _JumpIfError(hr, error, "SetProperty");
hr = prow->SetProperty( g_wszPropCRLThisUpdateDate, PROPTYPE_DATE | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(*pftThisUpdate), (BYTE const *) pftThisUpdate); _JumpIfError(hr, error, "SetProperty");
hr = prow->SetProperty( g_wszPropCRLNextUpdateDate, PROPTYPE_DATE | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(*pftNextUpdate), (BYTE const *) pftNextUpdate); _JumpIfError(hr, error, "SetProperty");
hr = prow->SetProperty( g_wszPropCRLThisPublishDate, PROPTYPE_DATE | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(*pftThisPublish), (BYTE const *) pftThisPublish); _JumpIfError(hr, error, "SetProperty");
hr = prow->SetProperty( g_wszPropCRLNextPublishDate, PROPTYPE_DATE | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(*pftNextPublish), (BYTE const *) pftNextPublish); _JumpIfError(hr, error, "SetProperty");
if (NULL != pftQuery) { hr = prow->SetProperty( g_wszPropCRLEffectiveDate, PROPTYPE_DATE | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(*pftQuery), (BYTE const *) pftQuery); _JumpIfError(hr, error, "SetProperty"); }
hr = prow->SetProperty( g_wszPropCRLPropagationCompleteDate, PROPTYPE_DATE | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(*pftPropagationComplete), (BYTE const *) pftPropagationComplete); _JumpIfError(hr, error, "SetProperty");
CRLPublishFlags = 0 == CRLMinBase? CPF_BASE : CPF_DELTA; if (fShadowDelta) { CRLPublishFlags |= CPF_SHADOW; } if (NULL != pwszUserName) { CRLPublishFlags |= CPF_MANUAL; } hr = prow->SetProperty( g_wszPropCRLPublishFlags, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(CRLPublishFlags), (BYTE const *) &CRLPublishFlags); _JumpIfError(hr, error, "SetProperty");
hr = prow->SetProperty( g_wszPropCRLRawCRL, PROPTYPE_BINARY | PROPCALLER_SERVER | PROPTABLE_CRL, cbCRL, pbCRL); _JumpIfError(hr, error, "SetProperty");
hr = prow->CommitTransaction(TRUE); _JumpIfError(hr, error, "CommitTransaction");
fCommitted = TRUE;
error: if (NULL != prow) { if (S_OK != hr && !fCommitted) { HRESULT hr2 = prow->CommitTransaction(FALSE); _PrintIfError(hr2, "CommitTransaction"); } prow->Release(); } return(hr); }
HRESULT crlCombineCRLError( IN ICertDBRow *prow, OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
OPTIONAL IN WCHAR const *pwszCRLError, OUT WCHAR **ppwszCRLErrorNew) { HRESULT hr; WCHAR *pwszCRLErrorOld = NULL; WCHAR *pwszCRLErrorNew = NULL; WCHAR *pwsz; DWORD cwc; DWORD cwc2;
*ppwszCRLErrorNew = NULL;
hr = PKCSGetProperty( prow, g_wszPropCRLPublishError, PROPTYPE_STRING | PROPCALLER_SERVER | PROPTABLE_CRL, NULL, (BYTE **) &pwszCRLErrorOld); _PrintIfError2(hr, "PKCSGetProperty", CERTSRV_E_PROPERTY_EMPTY);
cwc = 0; if (NULL != pwszCRLErrorOld) { pwsz = wcsstr(pwszCRLErrorOld, L"\n\n"); if (NULL == pwsz) { pwsz = pwszCRLErrorOld; } *pwsz = L'\0'; cwc = wcslen(pwszCRLErrorOld); if (0 != cwc) { cwc++; // newline separator
} } if (NULL != pwszUserName) { cwc2 = wcslen(g_pwszPublishedBy) + wcslen(pwszUserName); cwc += cwc2; } else { cwc++; } cwc += 2; // double newline separator
if (NULL != pwszCRLError) { cwc += wcslen(pwszCRLError); } pwszCRLErrorNew = (WCHAR *) LocalAlloc( LMEM_FIXED, (cwc + 1) * sizeof(WCHAR)); if (NULL == pwszCRLErrorNew) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); }
*pwszCRLErrorNew = L'\0'; if (NULL != pwszCRLErrorOld && L'\0' != *pwszCRLErrorOld) { wcscpy(pwszCRLErrorNew, pwszCRLErrorOld); wcscat(pwszCRLErrorNew, L"\n"); } if (NULL != pwszUserName) { pwsz = &pwszCRLErrorNew[wcslen(pwszCRLErrorNew)]; _snwprintf(pwsz, cwc2, g_pwszPublishedBy, pwszUserName); } else { wcscat(pwszCRLErrorNew, L"-"); } wcscat(pwszCRLErrorNew, L"\n\n"); // double newline separator
if (NULL != pwszCRLError) { wcscat(pwszCRLErrorNew, pwszCRLError); } CSASSERT(wcslen(pwszCRLErrorNew) <= cwc); CSASSERT( wcslen(pwszCRLErrorNew) + (NULL != pwszUserName? wcslen(L"%ws") : 0) == cwc); *ppwszCRLErrorNew = pwszCRLErrorNew; pwszCRLErrorNew = NULL; hr = S_OK;
error: if (NULL != pwszCRLErrorOld) { LocalFree(pwszCRLErrorOld); } if (NULL != pwszCRLErrorNew) { LocalFree(pwszCRLErrorNew); } return(hr); }
HRESULT crlUpdateCRLPublishStateInDB( IN DWORD RowId, IN FILETIME const *pftCurrent, IN HRESULT hrCRLPublish, IN DWORD CRLPublishFlags, OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
OPTIONAL IN WCHAR const *pwszCRLError) { HRESULT hr; ICertDBRow *prow = NULL; WCHAR *pwszCRLErrorNew = NULL; DWORD cb; DWORD dw; BOOL fCommitted = FALSE;
hr = g_pCertDB->OpenRow( PROPTABLE_CRL, RowId, NULL, &prow); _JumpIfError(hr, error, "OpenRow");
hr = prow->SetProperty( g_wszPropCRLLastPublished, PROPTYPE_DATE | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(*pftCurrent), (BYTE const *) pftCurrent); _JumpIfError(hr, error, "SetProperty");
cb = sizeof(dw); hr = prow->GetProperty( g_wszPropCRLPublishAttempts, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, &cb, (BYTE *) &dw); if (S_OK != hr) { dw = 0; } dw++;
hr = prow->SetProperty( g_wszPropCRLPublishAttempts, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(dw), (BYTE const *) &dw); _JumpIfError(hr, error, "SetProperty");
cb = sizeof(dw); hr = prow->GetProperty( g_wszPropCRLPublishFlags, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, &cb, (BYTE *) &dw); if (S_OK != hr) { dw = 0; } CRLPublishFlags |= (CPF_BASE | CPF_DELTA | CPF_SHADOW | CPF_MANUAL) & dw; if (S_OK == hrCRLPublish) { CRLPublishFlags |= CPF_COMPLETE; } hr = prow->SetProperty( g_wszPropCRLPublishFlags, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(CRLPublishFlags), (BYTE const *) &CRLPublishFlags); _JumpIfError(hr, error, "SetProperty");
// Always set error string property to clear out previous errors.
hr = prow->SetProperty( g_wszPropCRLPublishStatusCode, PROPTYPE_LONG | PROPCALLER_SERVER | PROPTABLE_CRL, sizeof(hrCRLPublish), (BYTE const *) &hrCRLPublish); _JumpIfError(hr, error, "SetProperty");
hr = crlCombineCRLError(prow, pwszUserName, pwszCRLError, &pwszCRLErrorNew); _JumpIfError(hr, error, "crlCombineCRLError");
hr = prow->SetProperty( g_wszPropCRLPublishError, PROPTYPE_STRING | PROPCALLER_SERVER | PROPTABLE_CRL, NULL == pwszCRLErrorNew? 0 : MAXDWORD, (BYTE const *) pwszCRLErrorNew); _JumpIfError(hr, error, "SetProperty");
hr = prow->CommitTransaction(TRUE); _JumpIfError(hr, error, "CommitTransaction");
fCommitted = TRUE;
error: if (NULL != prow) { if (S_OK != hr && !fCommitted) { HRESULT hr2 = prow->CommitTransaction(FALSE); _PrintIfError(hr2, "CommitTransaction"); } prow->Release(); } if (NULL != pwszCRLErrorNew) { LocalFree(pwszCRLErrorNew); } return(hr); }
HRESULT WriteCRLToDSAttribute( IN WCHAR const *pwszCRLDN, IN BOOL fDelta, IN BYTE const *pbCRL, IN DWORD cbCRL, OUT WCHAR **ppwszError) { HRESULT hr; DWORD ldaperr; BOOL fRebind = FALSE;
LDAPMod crlmod; struct berval crlberval; struct berval *crlVals[2]; LDAPMod *mods[2];
while (TRUE) { if (NULL == g_pld) { hr = myRobustLdapBind(&g_pld, FALSE); _JumpIfError(hr, error, "myRobustLdapBind"); }
mods[0] = &crlmod; mods[1] = NULL;
crlmod.mod_op = LDAP_MOD_BVALUES | LDAP_MOD_REPLACE; crlmod.mod_type = fDelta? wszDSDELTACRLATTRIBUTE : wszDSBASECRLATTRIBUTE; crlmod.mod_bvalues = crlVals;
crlVals[0] = &crlberval; crlVals[1] = NULL;
crlberval.bv_len = cbCRL; crlberval.bv_val = (char *) pbCRL;
ldaperr = ldap_modify_ext_s( g_pld, const_cast<WCHAR *>(pwszCRLDN), mods, NULL, NULL); hr = myHLdapError(g_pld, ldaperr, ppwszError); _PrintIfErrorStr(hr, "ldap_modify_ext_s", pwszCRLDN); if (fRebind || S_OK == hr) { break; } if (!myLdapRebindRequired(ldaperr, g_pld)) { _JumpErrorStr(hr, error, "ldap_modify_ext_s", pwszCRLDN); } fRebind = TRUE; if (NULL != g_pld) { ldap_unbind(g_pld); g_pld = NULL; } }
error: return(hr); }
HRESULT crlParseURLPrefix( IN WCHAR const *pwszIn, IN DWORD cwcPrefix, OUT WCHAR *pwcPrefix, OUT WCHAR const **ppwszOut) { HRESULT hr; WCHAR const *pwsz;
CSASSERT(6 <= cwcPrefix); wcscpy(pwcPrefix, L"file:"); *ppwszOut = pwszIn;
if (L'\\' != pwszIn[0] || L'\\' != pwszIn[1]) { pwsz = wcschr(pwszIn, L':'); if (NULL != pwsz) { DWORD cwc;
pwsz++; cwc = SAFE_SUBTRACT_POINTERS(pwsz, pwszIn); if (2 < cwc && cwc < cwcPrefix) { CopyMemory(pwcPrefix, pwszIn, cwc * sizeof(WCHAR)); pwcPrefix[cwc] = L'\0'; if (0 == lstrcmpi(pwcPrefix, L"file:") && L'/' == pwsz[0] && L'/' == pwsz[1]) { pwsz += 2; } *ppwszOut = pwsz; } } } hr = S_OK;
//error:
return(hr); }
VOID crlLogError( IN BOOL fDelta, IN BOOL fLdapURL, IN DWORD iKey, IN WCHAR const *pwszURL, IN WCHAR const *pwszError, IN HRESULT hrPublish) { HRESULT hr; WCHAR const *apwsz[6]; WORD cpwsz; WCHAR wszKey[11 + 1]; WCHAR awchr[cwcHRESULTSTRING]; WCHAR const *pwszMessageText = NULL; WCHAR *pwszHostName = NULL; DWORD LogMsg;
if (fLdapURL && NULL != g_pld) { myLdapGetDSHostName(g_pld, &pwszHostName); }
wsprintf(wszKey, L"%u", iKey); pwszMessageText = myGetErrorMessageText(hrPublish, TRUE); if (NULL == pwszMessageText) { pwszMessageText = myHResultToStringRaw(awchr, hrPublish); } cpwsz = 0; apwsz[cpwsz++] = wszKey; apwsz[cpwsz++] = pwszURL; apwsz[cpwsz++] = pwszMessageText;
LogMsg = fDelta? MSG_E_DELTA_CRL_PUBLICATION : MSG_E_BASE_CRL_PUBLICATION; if (NULL != pwszHostName) { LogMsg = fDelta? MSG_E_DELTA_CRL_PUBLICATION_HOST_NAME : MSG_E_BASE_CRL_PUBLICATION_HOST_NAME; } else { pwszHostName = L""; } apwsz[cpwsz++] = pwszHostName; apwsz[cpwsz++] = NULL != pwszError? L"\n" : L""; apwsz[cpwsz++] = NULL != pwszError? pwszError : L""; CSASSERT(ARRAYSIZE(apwsz) >= cpwsz);
if (CERTLOG_ERROR <= g_dwLogLevel) { hr = LogEvent(EVENTLOG_ERROR_TYPE, LogMsg, cpwsz, apwsz); _PrintIfError(hr, "LogEvent"); }
//error:
if (NULL != pwszMessageText && awchr != pwszMessageText) { LocalFree(const_cast<WCHAR *>(pwszMessageText)); } }
HRESULT crlWriteCRLToURL( IN BOOL fDelta, IN BOOL iKey, IN WCHAR const *pwszURL, IN BYTE const *pbCRL, IN DWORD cbCRL, OUT DWORD *pPublishFlags) { HRESULT hr; WCHAR *pwszDup = NULL; WCHAR const *pwsz2; WCHAR *pwszT; WCHAR awcPrefix[6]; // file:/ftp:/http:/ldap: and trailing '\0'
DWORD ErrorFlags; WCHAR *pwszError = NULL;
*pPublishFlags = 0;
ErrorFlags = CPF_BADURL_ERROR; hr = crlParseURLPrefix( pwszURL, ARRAYSIZE(awcPrefix), awcPrefix, &pwsz2); _JumpIfError(hr, error, "crlParseURLPrefix");
DBGPRINT(( DBG_SS_CERTSRV, "crlWriteCRLToURL: \"%ws\" %ws\n", awcPrefix, pwsz2)); if (0 == lstrcmpi(awcPrefix, L"file:")) { ErrorFlags = CPF_FILE_ERROR; hr = myDupString(pwsz2, &pwszDup); _JumpIfError(hr, error, "myDupString");
pwszT = wcsrchr(pwszDup, L'\\'); if (NULL != pwszT) { *pwszT = L'\0'; // for dir path, remove "\filename.crl"
}
// tricky
hr = WriteToLockedFile(pbCRL, cbCRL, pwszDup, pwsz2); _JumpIfError(hr, error, "WriteToLockedFile"); } else if (0 == lstrcmpi(awcPrefix, L"ftp:")) { ErrorFlags = CPF_FTP_ERROR; hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME); _JumpError(hr, error, "Publish to ftp:"); } else if (0 == lstrcmpi(awcPrefix, L"http:")) { ErrorFlags = CPF_HTTP_ERROR; hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME); _JumpError(hr, error, "Publish to http:"); } else if (0 == lstrcmpi(awcPrefix, L"ldap:")) { ErrorFlags = CPF_LDAP_ERROR; while (L'/' == *pwsz2) { pwsz2++; } hr = myDupString(pwsz2, &pwszDup); _JumpIfError(hr, error, "myDupString");
pwszT = wcschr(pwszDup, L'?'); if (NULL != pwszT) { *pwszT = L'\0'; } hr = WriteCRLToDSAttribute(pwszDup, fDelta, pbCRL, cbCRL, &pwszError); _JumpIfError(hr, error, "WriteCRLToDSAttribute"); } else { ErrorFlags = CPF_BADURL_ERROR; hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME); _JumpError(hr, error, "Publish to unknown URL type"); } CSASSERT(S_OK == hr);
error: if (S_OK != hr) { *pPublishFlags = ErrorFlags; crlLogError( fDelta, CPF_LDAP_ERROR == ErrorFlags, iKey, pwszURL, pwszError, hr); } if (NULL != pwszError) { LocalFree(pwszError); } if (NULL != pwszDup) { LocalFree(pwszDup); } return(hr); }
HRESULT crlWriteCRLToURLList( IN BOOL fDelta, IN DWORD iKey, IN WCHAR const * const *papwszURLs, IN BYTE const *pbCRL, IN DWORD cbCRL, IN OUT DWORD *pCRLPublishFlags, OUT WCHAR **ppwszCRLError) { HRESULT hr = S_OK; HRESULT hr2; WCHAR *pwszCRLError = NULL; DWORD PublishFlags;
*ppwszCRLError = NULL;
// publish this CRL in multiple places
if (NULL != papwszURLs) { WCHAR const * const *ppwsz;
for (ppwsz = papwszURLs; NULL != *ppwsz; ppwsz++) { PublishFlags = 0;
hr2 = crlWriteCRLToURL( fDelta, iKey, *ppwsz, pbCRL, cbCRL, &PublishFlags); *pCRLPublishFlags |= PublishFlags; if (S_OK != hr2) { DWORD cwc; WCHAR *pwsz;
if (S_OK == hr) { hr = hr2; // Save first error
} _PrintError(hr2, "crlWriteCRLToURL");
cwc = wcslen(*ppwsz) + 1; if (NULL != pwszCRLError) { cwc += wcslen(pwszCRLError) + 1; } pwsz = (WCHAR *) LocalAlloc(LMEM_FIXED, cwc * sizeof(WCHAR)); if (NULL == pwsz) { hr2 = E_OUTOFMEMORY; _PrintError(hr2, "LocalAlloc"); if (S_OK == hr) { hr = hr2; // Save first error
} } else { pwsz[0] = L'\0'; if (NULL != pwszCRLError) { wcscpy(pwsz, pwszCRLError); wcscat(pwsz, L"\n"); LocalFree(pwszCRLError); } wcscat(pwsz, *ppwsz); pwszCRLError = pwsz; } } } } *ppwszCRLError = pwszCRLError; pwszCRLError = NULL;
//error:
if (NULL != pwszCRLError) { LocalFree(pwszCRLError); } return(hr); }
HRESULT crlIsDeltaCRL( IN CRL_CONTEXT const *pCRL, OUT BOOL *pfIsDeltaCRL) { HRESULT hr; CERT_EXTENSION *pExt;
*pfIsDeltaCRL = FALSE; pExt = CertFindExtension( szOID_DELTA_CRL_INDICATOR, pCRL->pCrlInfo->cExtension, pCRL->pCrlInfo->rgExtension); if (NULL != pExt) { *pfIsDeltaCRL = TRUE; } hr = S_OK;
//error:
return(hr); }
HRESULT crlWriteCRLToCAStore( IN BOOL fDelta, IN DWORD iKey, IN BYTE const *pbCRL, IN DWORD cbCRL, IN CERT_CONTEXT const *pccCA) { HRESULT hr; HCERTSTORE hStore = NULL; CRL_CONTEXT const *pCRLStore = NULL; CRL_CONTEXT const *pCRLNew = NULL; BOOL fFound = FALSE;
hStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING, NULL, // hProv
CERT_SYSTEM_STORE_LOCAL_MACHINE, wszCA_CERTSTORE); if (NULL == hStore) { hr = myHLastError(); _JumpError(hr, error, "CertOpenStore"); }
while (TRUE) { DWORD dwCryptFlags; BOOL fIsDeltaCRL; CRL_CONTEXT const *pCRL;
dwCryptFlags = CERT_STORE_SIGNATURE_FLAG; pCRLStore = CertGetCRLFromStore( hStore, pccCA, pCRLStore, &dwCryptFlags); if (NULL == pCRLStore) { break; }
// delete this CRL from the store ONLY if the CRL signature matches
// this CA context's public key
if (0 != dwCryptFlags) { continue; // no match -- skip
}
hr = crlIsDeltaCRL(pCRLStore, &fIsDeltaCRL); _JumpIfError(hr, error, "crlIsDeltaCRL");
if (fIsDeltaCRL) { if (!fDelta) { continue; // no match -- skip Delta CRLs
} } else { if (fDelta) { continue; // no match -- skip Base CRLs
} }
// See if it has already been published
if (cbCRL == pCRLStore->cbCrlEncoded && 0 == memcmp(pbCRL, pCRLStore->pbCrlEncoded, cbCRL)) { fFound = TRUE; continue; // exact match -- already published
}
pCRL = CertDuplicateCRLContext(pCRLStore); if (!CertDeleteCRLFromStore(pCRL)) { hr = myHLastError(); _JumpError(hr, error, "CertDeleteCRLFromStore"); } }
if (!fFound) { pCRLNew = CertCreateCRLContext(X509_ASN_ENCODING, pbCRL, cbCRL); if (NULL == pCRLNew) { hr = myHLastError(); _JumpError(hr, error, "CertCreateCRLContext"); }
if (!CertAddCRLContextToStore( hStore, pCRLNew, CERT_STORE_ADD_ALWAYS, NULL)) { hr = myHLastError(); _JumpError(hr, error, "CertAddCRLContextToStore"); } } hr = S_OK;
error: if (S_OK != hr) { crlLogError(fDelta, FALSE, iKey, g_pwszIntermediateCAStore, NULL, hr); } if (NULL != pCRLNew) { CertFreeCRLContext(pCRLNew); } if (NULL != pCRLStore) { CertFreeCRLContext(pCRLStore); } if (NULL != hStore) { CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG); } return(hr); }
HRESULT crlPublishGeneratedCRL( IN DWORD RowId, IN FILETIME const *pftCurrent, OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
IN BOOL fDelta, IN DWORD iKey, IN BYTE const *pbCRL, IN DWORD cbCRL, IN CACTX const *pCAContext, OUT BOOL *pfRetryNeeded, OUT HRESULT *phrCRLPublish) { HRESULT hr; HRESULT hrCRLPublish; DWORD CRLPublishFlags; WCHAR *pwszCRLError = NULL;
*pfRetryNeeded = FALSE; hrCRLPublish = S_OK; CRLPublishFlags = 0;
hr = crlWriteCRLToCAStore(fDelta, iKey, pbCRL, cbCRL, pCAContext->pccCA); if (S_OK != hr) { _PrintError(hr, "crlWriteCRLToCAStore"); hrCRLPublish = hr; CRLPublishFlags |= CPF_CASTORE_ERROR; }
hr = crlWriteCRLToURLList( fDelta, iKey, fDelta? pCAContext->papwszDeltaCRLFiles : pCAContext->papwszCRLFiles, pbCRL, cbCRL, &CRLPublishFlags, &pwszCRLError); if (S_OK != hr) { _PrintError(hr, "crlWriteCRLToURLList"); if (S_OK == hrCRLPublish) { hrCRLPublish = hr; // save first error
} } if (S_OK != hrCRLPublish) { *pfRetryNeeded = TRUE; } hr = crlUpdateCRLPublishStateInDB( RowId, pftCurrent, hrCRLPublish, CRLPublishFlags, pwszUserName, pwszCRLError); _JumpIfError(hr, error, "crlUpdateCRLPublishStateInDB");
error: *phrCRLPublish = hrCRLPublish; if (NULL != pwszCRLError) { LocalFree(pwszCRLError); } return(hr); }
HRESULT crlSignAndSaveCRL( IN DWORD CRLNumber, IN DWORD CRLNumberBaseMin, // 0 implies Base CRL; else Delta CRL
OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
IN BOOL fShadowDelta, // empty delta CRL with new MinBaseCRL
IN CACTX const *pCAContext, IN DWORD cCRL, IN CRL_ENTRY *aCRL, IN FILETIME const *pftCurrent, IN FILETIME const *pftThisUpdate, // includes skew
IN FILETIME const *pftNextUpdate, // includes skew & overlap
IN FILETIME const *pftThisPublish, IN FILETIME const *pftNextPublish, OPTIONAL IN FILETIME const *pftQuery, IN FILETIME const *pftPropagationComplete, OUT BOOL *pfRetryNeeded, OUT HRESULT *phrCRLPublish) { HRESULT hr; CRL_INFO CRLInfo; DWORD i; DWORD cb; DWORD cbCRL; BYTE *pbCrlEncoded = NULL; BYTE *pbCRL = NULL; #define CCRLEXT 6
CERT_EXTENSION aext[CCRLEXT]; BYTE *apbFree[CCRLEXT]; DWORD cpbFree = 0; DWORD RowId;
*pfRetryNeeded = FALSE; *phrCRLPublish = S_OK;
ZeroMemory(&CRLInfo, sizeof(CRLInfo)); CRLInfo.dwVersion = CRL_V2; CRLInfo.SignatureAlgorithm.pszObjId = pCAContext->pszObjIdSignatureAlgorithm; CRLInfo.Issuer.pbData = pCAContext->pccCA->pCertInfo->Subject.pbData; CRLInfo.Issuer.cbData = pCAContext->pccCA->pCertInfo->Subject.cbData; CRLInfo.ThisUpdate = *pftThisUpdate; CRLInfo.NextUpdate = *pftNextUpdate; CRLInfo.cCRLEntry = cCRL; CRLInfo.rgCRLEntry = aCRL;
CRLInfo.cExtension = 0; CRLInfo.rgExtension = aext; ZeroMemory(aext, sizeof(aext));
if (NULL != pCAContext->KeyAuthority2CRL.pbData) { aext[CRLInfo.cExtension].pszObjId = szOID_AUTHORITY_KEY_IDENTIFIER2; if (EDITF_ENABLEAKICRITICAL & g_CRLEditFlags) { aext[CRLInfo.cExtension].fCritical = TRUE; } aext[CRLInfo.cExtension].Value = pCAContext->KeyAuthority2CRL; CRLInfo.cExtension++; }
if (!myEncodeObject( X509_ASN_ENCODING, X509_INTEGER, &pCAContext->NameId, 0, CERTLIB_USE_LOCALALLOC, &aext[CRLInfo.cExtension].Value.pbData, &aext[CRLInfo.cExtension].Value.cbData)) { hr = myHLastError(); _JumpError(hr, error, "myEncodeObject"); } aext[CRLInfo.cExtension].pszObjId = szOID_CERTSRV_CA_VERSION; apbFree[cpbFree++] = aext[CRLInfo.cExtension].Value.pbData, CRLInfo.cExtension++;
if (!myEncodeObject( X509_ASN_ENCODING, X509_INTEGER, &CRLNumber, 0, CERTLIB_USE_LOCALALLOC, &aext[CRLInfo.cExtension].Value.pbData, &aext[CRLInfo.cExtension].Value.cbData)) { hr = myHLastError(); _JumpError(hr, error, "myEncodeObject"); } aext[CRLInfo.cExtension].pszObjId = szOID_CRL_NUMBER; apbFree[cpbFree++] = aext[CRLInfo.cExtension].Value.pbData; if ((CRLF_CRLNUMBER_CRITICAL & g_dwCRLFlags) && 0 == CRLNumberBaseMin) { aext[CRLInfo.cExtension].fCritical = TRUE; } CRLInfo.cExtension++;
// NextPublish is the earliest the client should look for a newer CRL.
if (!myEncodeObject( X509_ASN_ENCODING, X509_CHOICE_OF_TIME, pftNextPublish, 0, CERTLIB_USE_LOCALALLOC, &aext[CRLInfo.cExtension].Value.pbData, &aext[CRLInfo.cExtension].Value.cbData)) { hr = myHLastError(); _JumpError(hr, error, "myEncodeObject"); } aext[CRLInfo.cExtension].pszObjId = szOID_CRL_NEXT_PUBLISH; apbFree[cpbFree++] = aext[CRLInfo.cExtension].Value.pbData, CRLInfo.cExtension++;
if (0 != CRLNumberBaseMin) // if Delta CRL
{ if (!myEncodeObject( X509_ASN_ENCODING, X509_INTEGER, &CRLNumberBaseMin, 0, CERTLIB_USE_LOCALALLOC, &aext[CRLInfo.cExtension].Value.pbData, &aext[CRLInfo.cExtension].Value.cbData)) { hr = myHLastError(); _JumpError(hr, error, "myEncodeObject"); } aext[CRLInfo.cExtension].pszObjId = szOID_DELTA_CRL_INDICATOR; aext[CRLInfo.cExtension].fCritical = TRUE; apbFree[cpbFree++] = aext[CRLInfo.cExtension].Value.pbData, CRLInfo.cExtension++;
// Add a CDP to base and delta CRLs to make it easier to manually
// publish an off-line CA's CRLs to the correct DS location.
if (NULL != pCAContext->CDPCRLDelta.pbData) { aext[CRLInfo.cExtension].pszObjId = szOID_CRL_SELF_CDP; aext[CRLInfo.cExtension].Value = pCAContext->CDPCRLDelta; CRLInfo.cExtension++; } } else { // else if Base CRL (and if delta CRLs are enabled)
if (!g_fDeltaCRLPublishDisabled && NULL != pCAContext->CDPCRLFreshest.pbData) { aext[CRLInfo.cExtension].pszObjId = szOID_FRESHEST_CRL; aext[CRLInfo.cExtension].Value = pCAContext->CDPCRLFreshest; CRLInfo.cExtension++; }
// Add a CDP to base and delta CRLs to make it easier to manually
// publish an off-line CA's CRLs to the correct DS location.
if (NULL != pCAContext->CDPCRLBase.pbData) { aext[CRLInfo.cExtension].pszObjId = szOID_CRL_SELF_CDP; aext[CRLInfo.cExtension].Value = pCAContext->CDPCRLBase; CRLInfo.cExtension++; } } CSASSERT(ARRAYSIZE(aext) >= CRLInfo.cExtension);
if (!myEncodeObject( X509_ASN_ENCODING, X509_CERT_CRL_TO_BE_SIGNED, &CRLInfo, 0, CERTLIB_USE_LOCALALLOC, &pbCrlEncoded, // pbEncoded
&cb)) { hr = myHLastError(); _JumpError(hr, error, "myEncodeObject"); }
hr = myEncodeSignedContent( pCAContext->hProvCA, X509_ASN_ENCODING, pCAContext->pszObjIdSignatureAlgorithm, pbCrlEncoded, cb, CERTLIB_USE_LOCALALLOC, &pbCRL, &cbCRL); // use LocalAlloc*
_JumpIfError(hr, error, "myEncodeSignedContent");
hr = crlWriteCRLToDB( CRLNumber, // CRLNumber
CRLNumberBaseMin, // CRLMinBase: 0 implies Base CRL
pwszUserName, fShadowDelta, pCAContext->NameId, // CRLNameId
cCRL, // CRLCount
&CRLInfo.ThisUpdate, // pftThisUpdate
&CRLInfo.NextUpdate, // pftNextUpdate
pftThisPublish, // pftThisPublish
pftNextPublish, // pftNextPublish
pftQuery, pftPropagationComplete, pbCRL, // pbCRL
cbCRL, // cbCRL
&RowId); _JumpIfError(hr, error, "crlWriteCRLToDB");
hr = crlPublishGeneratedCRL( RowId, pftCurrent, pwszUserName, 0 != CRLNumberBaseMin, // fDelta
pCAContext->iKey, pbCRL, // pbCRL
cbCRL, // cbCRL
pCAContext, pfRetryNeeded, phrCRLPublish); _JumpIfError(hr, error, "crlPublishGeneratedCRL");
error: CSASSERT(ARRAYSIZE(aext) >= CRLInfo.cExtension); CSASSERT(ARRAYSIZE(apbFree) >= cpbFree); for (i = 0; i < cpbFree; i++) { CSASSERT(NULL != apbFree[i]); LocalFree(apbFree[i]); } if (NULL != pbCrlEncoded) { LocalFree(pbCrlEncoded); } if (NULL != pbCRL) { LocalFree(pbCRL); } return(myHError(hr)); }
///////////////////////////////////////////////////
// crlPublishCRLFromCAContext is called to build and save one CRL.
//
HRESULT crlPublishCRLFromCAContext( IN DWORD CRLNumber, IN DWORD CRLNumberBaseMin, // 0 implies Base CRL; else Delta CRL
OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
IN BOOL fShadowDelta, // empty delta CRL with new MinBaseCRL
IN CACTX const *pCAContext, IN FILETIME const *pftCurrent, IN FILETIME ftThisUpdate, // clamped by CA cert
IN OUT FILETIME *pftNextUpdate, // clamped by CA cert
OPTIONAL OUT BOOL *pfClamped, OPTIONAL IN FILETIME const *pftQuery, IN FILETIME const *pftThisPublish, IN FILETIME const *pftNextPublish, IN FILETIME const *pftLastPublishBase, IN FILETIME const *pftPropagationComplete, OUT BOOL *pfRetryNeeded, OUT HRESULT *phrPublish) { HRESULT hr; DWORD cCRL = 0; CRL_ENTRY *aCRL = NULL; VOID *pvBlockSerial = NULL; CERT_INFO const *pCertInfo = pCAContext->pccCA->pCertInfo;
*pfRetryNeeded = FALSE; *phrPublish = S_OK; __try { if (!fShadowDelta) { hr = crlBuildCRLArray( 0 != CRLNumberBaseMin, // fDelta
pftQuery, pftThisPublish, pftLastPublishBase, pCAContext->iKey, &cCRL, &aCRL, &pvBlockSerial); _JumpIfError(hr, error, "crlBuildCRLArray"); }
// Ensure it is not before the CA certificate's start date.
if (0 > CompareFileTime(&ftThisUpdate, &pCertInfo->NotBefore)) { // clamp
ftThisUpdate = pCertInfo->NotBefore; }
// Ensure it is not after the CA certificate's end date.
if (NULL != pfClamped) { //init to FALSE
*pfClamped = FALSE; }
if (0 == (CRLF_PUBLISH_EXPIRED_CERT_CRLS & g_dwCRLFlags) && 0 < CompareFileTime(pftNextUpdate, &pCertInfo->NotAfter)) { // clamp
*pftNextUpdate = pCertInfo->NotAfter; if (NULL != pfClamped) { *pfClamped = TRUE; } } #ifdef DBG_CERTSRV_DEBUG_PRINT
{ WCHAR *pwszNow = NULL; WCHAR *pwszQuery = NULL; WCHAR *pwszThisUpdate = NULL; WCHAR *pwszNextUpdate = NULL; WCHAR const *pwszCRLType = 0 == CRLNumberBaseMin? L"Base" : L"Delta";
myGMTFileTimeToWszLocalTime(pftThisPublish, TRUE, &pwszNow); if (NULL != pftQuery) { myGMTFileTimeToWszLocalTime(pftQuery, TRUE, &pwszQuery); } myGMTFileTimeToWszLocalTime(&ftThisUpdate, TRUE, &pwszThisUpdate); myGMTFileTimeToWszLocalTime(pftNextUpdate, TRUE, &pwszNextUpdate);
DBGPRINT(( DBG_SS_ERROR | DBG_SS_CERTSRV, "crlPublishCRLFromCAContext(tid=%d, CA Version=%u.%u): %ws CRL %u,%hs %u\n" " %ws CRL Publishing now(%ws)\n" " %ws CRL Query(%ws)\n" " %ws CRL ThisUpdate(%ws)\n" " %ws CRL NextUpdate(%ws)\n", GetCurrentThreadId(), pCAContext->iCert, pCAContext->iKey, pwszCRLType, CRLNumber, 0 == CRLNumberBaseMin? "" : " Min Base", CRLNumberBaseMin,
pwszCRLType, pwszNow,
pwszCRLType, NULL != pftQuery? pwszQuery : L"None",
pwszCRLType, pwszThisUpdate,
pwszCRLType, pwszNextUpdate)); if (NULL != pwszNow) { LocalFree(pwszNow); } if (NULL != pwszQuery) { LocalFree(pwszQuery); } if (NULL != pwszThisUpdate) { LocalFree(pwszThisUpdate); } if (NULL != pwszNextUpdate) { LocalFree(pwszNextUpdate); } } #endif //DBG_CERTSRV_DEBUG_PRINT
hr = CertSrvTestServerState(); _JumpIfError(hr, error, "CertSrvTestServerState");
hr = crlSignAndSaveCRL( CRLNumber, CRLNumberBaseMin, pwszUserName, fShadowDelta, pCAContext, cCRL, aCRL, pftCurrent, &ftThisUpdate, pftNextUpdate, pftThisPublish, // - no skew or overlap
pftNextPublish, // no skew
pftQuery, pftPropagationComplete, pfRetryNeeded, phrPublish); _JumpIfError(hr, error, "crlSignAndSaveCRL");
CONSOLEPRINT4(( DBG_SS_CERTSRV, "Published %hs CRL #%u for key %u.%u\n", 0 == CRLNumberBaseMin? "Base" : "Delta", CRLNumber, pCAContext->iCert, pCAContext->iKey));
CSASSERT(S_OK == hr); } __except(hr = myHEXCEPTIONCODE(), EXCEPTION_EXECUTE_HANDLER) { }
error: crlFreeCRLArray(pvBlockSerial, aCRL); CSASSERT(S_OK == hr || FAILED(hr)); return(hr); }
DWORD g_aColCRLNumber[] = {
#define ICOL_CRLNUMBER 0
DTI_CRLTABLE | DTL_NUMBER, };
HRESULT crlGetNextCRLNumber( OUT DWORD *pdwCRLNumber) { HRESULT hr; CERTVIEWRESTRICTION acvr[1]; CERTVIEWRESTRICTION *pcvr; IEnumCERTDBRESULTROW *pView = NULL; DWORD Zero = 0; CERTDBRESULTROW aResult[1]; CERTDBRESULTROW *pResult; DWORD celtFetched; BOOL fResultActive = FALSE;
*pdwCRLNumber = 1;
// Set up restrictions as follows:
pcvr = acvr;
// CRLNumber > 0 (indexed column)
pcvr->ColumnIndex = DTI_CRLTABLE | DTL_NUMBER; pcvr->SeekOperator = CVR_SEEK_GT; pcvr->SortOrder = CVR_SORT_DESCEND; // highest CRL Number first
pcvr->pbValue = (BYTE *) &Zero; pcvr->cbValue = sizeof(Zero); pcvr++;
CSASSERT(ARRAYSIZE(acvr) == SAFE_SUBTRACT_POINTERS(pcvr, acvr));
hr = g_pCertDB->OpenView( ARRAYSIZE(acvr), acvr, ARRAYSIZE(g_aColCRLNumber), g_aColCRLNumber, 0, // no worker thread
&pView); _JumpIfError(hr, error, "OpenView");
hr = pView->Next(ARRAYSIZE(aResult), aResult, &celtFetched); if (S_FALSE == hr) { if (0 == celtFetched) { hr = S_OK; goto error; } } _JumpIfError(hr, error, "Next");
fResultActive = TRUE;
CSASSERT(ARRAYSIZE(aResult) == celtFetched);
pResult = &aResult[0];
CSASSERT(ARRAYSIZE(g_aColCRLNumber) == pResult->ccol); CSASSERT(NULL != pResult->acol[ICOL_CRLNUMBER].pbValue); CSASSERT(PROPTYPE_LONG == (PROPTYPE_MASK & pResult->acol[ICOL_CRLNUMBER].Type)); CSASSERT(sizeof(*pdwCRLNumber) == pResult->acol[ICOL_CRLNUMBER].cbValue);
*pdwCRLNumber = 1 + *(DWORD *) pResult->acol[ICOL_CRLNUMBER].pbValue; hr = S_OK;
error: if (NULL != pView) { if (fResultActive) { pView->ReleaseResultRow(celtFetched, aResult); } pView->Release(); } DBGPRINT(( DBG_SS_CERTSRVI, "crlGetNextCRLNumber -> %u\n", *pdwCRLNumber)); return(hr); } #undef ICOL_CRLNUMBER
//+--------------------------------------------------------------------------
// crlGetBaseCRLInfo -- get database column data for the most recent Base CRL
//
//---------------------------------------------------------------------------
DWORD g_aColBaseCRLInfo[] = {
#define ICOLBI_CRLNUMBER 0
DTI_CRLTABLE | DTL_NUMBER,
#define ICOLBI_CRLTHISUPDATE 1
DTI_CRLTABLE | DTL_THISUPDATEDATE,
#define ICOLBI_CRLNEXTUPDATE 2
DTI_CRLTABLE | DTL_NEXTUPDATEDATE,
#define ICOLBI_CRLNAMEID 3
DTI_CRLTABLE | DTL_NAMEID, };
HRESULT crlGetBaseCRLInfo( IN FILETIME const *pftCurrent, IN BOOL fOldestUnexpiredBase, // else newest propagated CRL
OUT DWORD *pdwRowId, OUT DWORD *pdwCRLNumber, OUT FILETIME *pftThisUpdate) { HRESULT hr; CERTVIEWRESTRICTION acvr[2]; CERTVIEWRESTRICTION *pcvr; IEnumCERTDBRESULTROW *pView = NULL; DWORD Zero = 0; CERTDBRESULTROW aResult[1]; CERTDBRESULTROW *pResult; DWORD celtFetched; BOOL fResultActive = FALSE; BOOL fSaveCRLInfo;
DWORD RowId = 0; DWORD CRLNumber; FILETIME ftThisUpdate; FILETIME ftNextUpdate;
*pdwRowId = 0; *pdwCRLNumber = 0; pftThisUpdate->dwHighDateTime = 0; pftThisUpdate->dwLowDateTime = 0;
if (CRLF_DELTA_USE_OLDEST_UNEXPIRED_BASE & g_dwCRLFlags) { fOldestUnexpiredBase = TRUE; }
// Set up restrictions as follows:
pcvr = acvr; if (fOldestUnexpiredBase) { // NextUpdate >= now
pcvr->ColumnIndex = DTI_CRLTABLE | DTL_NEXTUPDATEDATE; pcvr->SeekOperator = CVR_SEEK_GE; } else // else newest propagated CRL
{ // PropagationComplete < now
pcvr->ColumnIndex = DTI_CRLTABLE | DTL_PROPAGATIONCOMPLETEDATE; pcvr->SeekOperator = CVR_SEEK_LT; } pcvr->SortOrder = CVR_SORT_DESCEND; // Newest CRL first
pcvr->pbValue = (BYTE *) pftCurrent; pcvr->cbValue = sizeof(*pftCurrent); pcvr++;
// CRL Minimum Base == 0 (to eliminate delta CRLs)
pcvr->ColumnIndex = DTI_CRLTABLE | DTL_MINBASE; pcvr->SeekOperator = CVR_SEEK_EQ; pcvr->SortOrder = CVR_SORT_NONE; pcvr->pbValue = (BYTE *) &Zero; pcvr->cbValue = sizeof(Zero); pcvr++;
CSASSERT(ARRAYSIZE(acvr) == SAFE_SUBTRACT_POINTERS(pcvr, acvr));
hr = g_pCertDB->OpenView( ARRAYSIZE(acvr), acvr, ARRAYSIZE(g_aColBaseCRLInfo), g_aColBaseCRLInfo, 0, // no worker thread
&pView); _JumpIfError(hr, error, "OpenView");
while (0 == RowId || fOldestUnexpiredBase) { hr = pView->Next(ARRAYSIZE(aResult), aResult, &celtFetched); if (S_FALSE == hr) { CSASSERT(0 == celtFetched); if (0 != RowId) { break; } hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } _JumpIfError(hr, error, "Next: no matching base CRL");
fResultActive = TRUE;
CSASSERT(ARRAYSIZE(aResult) == celtFetched);
pResult = &aResult[0];
CSASSERT(ARRAYSIZE(g_aColBaseCRLInfo) == pResult->ccol);
CSASSERT(NULL != pResult->acol[ICOLBI_CRLNUMBER].pbValue); CSASSERT(PROPTYPE_LONG == (PROPTYPE_MASK & pResult->acol[ICOLBI_CRLNUMBER].Type)); CSASSERT(sizeof(DWORD) == pResult->acol[ICOLBI_CRLNUMBER].cbValue);
CSASSERT(NULL != pResult->acol[ICOLBI_CRLTHISUPDATE].pbValue); CSASSERT(PROPTYPE_DATE == (PROPTYPE_MASK & pResult->acol[ICOLBI_CRLTHISUPDATE].Type)); CSASSERT(sizeof(FILETIME) == pResult->acol[ICOLBI_CRLTHISUPDATE].cbValue);
CSASSERT(NULL != pResult->acol[ICOLBI_CRLNEXTUPDATE].pbValue); CSASSERT(PROPTYPE_DATE == (PROPTYPE_MASK & pResult->acol[ICOLBI_CRLNEXTUPDATE].Type)); CSASSERT(sizeof(FILETIME) == pResult->acol[ICOLBI_CRLNEXTUPDATE].cbValue);
DBGPRINT((DBG_SS_CERTSRVI, "Query:RowId: %u\n", pResult->rowid)); DBGPRINT((DBG_SS_CERTSRVI, "Query:CRLNumber: %u\n", *(DWORD *) pResult->acol[ICOLBI_CRLNUMBER].pbValue)); DBGPRINT((DBG_SS_CERTSRVI, "Query:NameId: 0x%x\n", *(DWORD *) pResult->acol[ICOLBI_CRLNAMEID].pbValue)); DBGPRINTTIME(NULL, "Query:ThisUpdate", DPT_DATE, *(FILETIME *) pResult->acol[ICOLBI_CRLNEXTUPDATE].pbValue); DBGPRINTTIME(NULL, "Query:NextUpdate", DPT_DATE, *(FILETIME *) pResult->acol[ICOLBI_CRLTHISUPDATE].pbValue);
if (0 == RowId) { // save first matching row info
fSaveCRLInfo = TRUE; } else { // save row info, if looking for
// oldest unexpired base & this CRL expires before the saved CRL
// +1 if first > second -- saved > this
CSASSERT(fOldestUnexpiredBase);
fSaveCRLInfo = 0 < CompareFileTime( &ftNextUpdate, (FILETIME *) pResult->acol[ICOLBI_CRLNEXTUPDATE].pbValue); } if (fSaveCRLInfo) { CRLNumber = *(DWORD *) pResult->acol[ICOLBI_CRLNUMBER].pbValue; ftThisUpdate = *(FILETIME *) pResult->acol[ICOLBI_CRLTHISUPDATE].pbValue; ftNextUpdate = *(FILETIME *) pResult->acol[ICOLBI_CRLNEXTUPDATE].pbValue; RowId = pResult->rowid; DBGPRINT(( DBG_SS_CERTSRVI, "Query: SAVED RowId=%u CRLNumber=%u\n", pResult->rowid, CRLNumber)); DBGPRINTTIME(NULL, "ftThisUpdate", DPT_DATE, ftThisUpdate); } pView->ReleaseResultRow(celtFetched, aResult); fResultActive = FALSE; }
*pdwRowId = RowId; *pdwCRLNumber = CRLNumber; *pftThisUpdate = ftThisUpdate; DBGPRINTTIME(NULL, "*pftThisUpdate", DPT_DATE, *pftThisUpdate); DBGPRINTTIME(NULL, "ftNextUpdate", DPT_DATE, ftNextUpdate); hr = S_OK;
error: if (NULL != pView) { if (fResultActive) { pView->ReleaseResultRow(celtFetched, aResult); } pView->Release(); } DBGPRINT(( DBG_SS_CERTSRV, "crlGetBaseCRLInfo -> RowId=%u, CRL=%u\n", *pdwRowId, *pdwCRLNumber)); return(hr); } #undef ICOLBI_CRLNUMBER
#undef ICOLBI_CRLTHISUPDATE
#undef ICOLBI_CRLNEXTUPDATE
#undef ICOLBI_CRLNAMEID
DWORD g_aColRepublishCRLInfo[] = {
#define ICOLRI_CRLNUMBER 0
DTI_CRLTABLE | DTL_NUMBER,
#define ICOLRI_CRLNAMEID 1
DTI_CRLTABLE | DTL_NAMEID,
#define ICOLRI_CRLPUBLISHFLAGS 2
DTI_CRLTABLE | DTL_PUBLISHFLAGS,
#define ICOLRI_CRLTHISUPDATE 3
DTI_CRLTABLE | DTL_THISUPDATEDATE,
#define ICOLRI_CRLNEXTUPDATE 4
DTI_CRLTABLE | DTL_NEXTUPDATEDATE,
#define ICOLRI_CRLRAWCRL 5
DTI_CRLTABLE | DTL_RAWCRL, };
HRESULT crlGetRowIdAndCRL( IN BOOL fDelta, IN CACTX *pCAContext, OUT DWORD *pdwRowId, OUT DWORD *pcbCRL, OPTIONAL OUT BYTE **ppbCRL, OPTIONAL OUT DWORD *pdwCRLPublishFlags) { HRESULT hr; CERTVIEWRESTRICTION acvr[4]; CERTVIEWRESTRICTION *pcvr; IEnumCERTDBRESULTROW *pView = NULL; DWORD Zero = 0; DWORD NameIdMin; DWORD NameIdMax; CERTDBRESULTROW aResult[1]; CERTDBRESULTROW *pResult; DWORD celtFetched; BOOL fResultActive = FALSE; FILETIME ftCurrent; DWORD RowId = 0; BYTE *pbCRL = NULL; DWORD cbCRL;
*pdwRowId = 0; *pcbCRL = 0; if (NULL != ppbCRL) { *ppbCRL = NULL; }
if (NULL != pdwCRLPublishFlags) { *pdwCRLPublishFlags = 0; } GetSystemTimeAsFileTime(&ftCurrent);
DBGPRINT(( DBG_SS_CERTSRVI, "crlGetRowIdAndCRL(%ws, NameId=%x)\n", fDelta? L"Delta" : L"Base", pCAContext->NameId));
// Set up restrictions as follows:
pcvr = acvr;
// RowId > 0
pcvr->ColumnIndex = DTI_CRLTABLE | DTL_ROWID; pcvr->SeekOperator = CVR_SEEK_GE; pcvr->SortOrder = CVR_SORT_DESCEND; // Newest CRL first
pcvr->pbValue = (BYTE *) &Zero; pcvr->cbValue = sizeof(Zero); pcvr++;
if (fDelta) { // CRL Minimum Base > 0 (to eliminate base CRLs)
pcvr->SeekOperator = CVR_SEEK_GT; } else { // CRL Minimum Base == 0 (to eliminate delta CRLs)
pcvr->SeekOperator = CVR_SEEK_EQ; } pcvr->ColumnIndex = DTI_CRLTABLE | DTL_MINBASE; pcvr->SortOrder = CVR_SORT_NONE; pcvr->pbValue = (BYTE *) &Zero; pcvr->cbValue = sizeof(Zero); pcvr++;
// NameId >= MAKECANAMEID(iCert == 0, pCAContext->iKey)
NameIdMin = MAKECANAMEID(0, pCAContext->iKey); pcvr->ColumnIndex = DTI_CRLTABLE | DTL_NAMEID; pcvr->SeekOperator = CVR_SEEK_GE; pcvr->SortOrder = CVR_SORT_NONE; pcvr->pbValue = (BYTE *) &NameIdMin; pcvr->cbValue = sizeof(NameIdMin); pcvr++;
// NameId <= MAKECANAMEID(iCert == _16BITMASK, pCAContext->iKey)
NameIdMax = MAKECANAMEID(_16BITMASK, pCAContext->iKey); pcvr->ColumnIndex = DTI_CRLTABLE | DTL_NAMEID; pcvr->SeekOperator = CVR_SEEK_LE; pcvr->SortOrder = CVR_SORT_NONE; pcvr->pbValue = (BYTE *) &NameIdMax; pcvr->cbValue = sizeof(NameIdMax); pcvr++;
CSASSERT(ARRAYSIZE(acvr) == SAFE_SUBTRACT_POINTERS(pcvr, acvr));
hr = g_pCertDB->OpenView( ARRAYSIZE(acvr), acvr, ((NULL != ppbCRL) ? (DWORD) ARRAYSIZE(g_aColRepublishCRLInfo) : (DWORD) ARRAYSIZE(g_aColRepublishCRLInfo) - 1 ), // explicitly describe expected return value
g_aColRepublishCRLInfo, 0, // no worker thread
&pView); _JumpIfError(hr, error, "OpenView");
while (0 == RowId) { hr = pView->Next(ARRAYSIZE(aResult), aResult, &celtFetched); if (S_FALSE == hr) { CSASSERT(0 == celtFetched); hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } _JumpIfErrorStr( hr, error, "Next: no matching CRL", fDelta? L"delta" : L"base");
fResultActive = TRUE;
CSASSERT(ARRAYSIZE(aResult) == celtFetched);
pResult = &aResult[0];
CSASSERT(ARRAYSIZE(g_aColRepublishCRLInfo) == pResult->ccol);
// verify CRLNumber data & schema
CSASSERT(NULL != pResult->acol[ICOLRI_CRLNUMBER].pbValue); CSASSERT(PROPTYPE_LONG == (PROPTYPE_MASK & pResult->acol[ICOLRI_CRLNUMBER].Type)); CSASSERT(sizeof(DWORD) == pResult->acol[ICOLRI_CRLNUMBER].cbValue);
// verify ThisUpdate data & schema
CSASSERT(NULL != pResult->acol[ICOLRI_CRLTHISUPDATE].pbValue); CSASSERT(PROPTYPE_DATE == (PROPTYPE_MASK & pResult->acol[ICOLRI_CRLTHISUPDATE].Type)); CSASSERT(sizeof(FILETIME) == pResult->acol[ICOLRI_CRLTHISUPDATE].cbValue);
// verify NextUpdate data & schema
CSASSERT(NULL != pResult->acol[ICOLRI_CRLNEXTUPDATE].pbValue); CSASSERT(PROPTYPE_DATE == (PROPTYPE_MASK & pResult->acol[ICOLRI_CRLNEXTUPDATE].Type)); CSASSERT(sizeof(FILETIME) == pResult->acol[ICOLRI_CRLNEXTUPDATE].cbValue);
// verify RawCRL data & schema
if (NULL != ppbCRL) { CSASSERT(NULL != pResult->acol[ICOLRI_CRLRAWCRL].pbValue); CSASSERT(PROPTYPE_BINARY == (PROPTYPE_MASK & pResult->acol[ICOLRI_CRLRAWCRL].Type)); }
// DBGPRINT query results
DBGPRINT((DBG_SS_CERTSRVI, "Query:RowId: %u\n", pResult->rowid)); DBGPRINT((DBG_SS_CERTSRVI, "Query:CRLNumber: %u\n", *(DWORD *) pResult->acol[ICOLRI_CRLNUMBER].pbValue)); DBGPRINT((DBG_SS_CERTSRVI, "Query:NameId: 0x%x\n", *(DWORD *) pResult->acol[ICOLRI_CRLNAMEID].pbValue)); DBGPRINTTIME(NULL, "Query:ThisUpdate", DPT_DATE, *(FILETIME *) pResult->acol[ICOLRI_CRLNEXTUPDATE].pbValue); DBGPRINTTIME(NULL, "Query:NextUpdate", DPT_DATE, *(FILETIME *) pResult->acol[ICOLRI_CRLTHISUPDATE].pbValue); if (NULL != ppbCRL) { DBGPRINT((DBG_SS_CERTSRVI, "Query:RawCRL: cb=%x\n", pResult->acol[ICOLRI_CRLRAWCRL].cbValue)); } if (NULL != pResult->acol[ICOLRI_CRLPUBLISHFLAGS].pbValue) { DBGPRINT(( DBG_SS_CERTSRVI, "Query:PublishFlags: f=%x\n", *(DWORD *) pResult->acol[ICOLRI_CRLPUBLISHFLAGS].pbValue)); } if (0 < CompareFileTime( (FILETIME *) pResult->acol[ICOLRI_CRLTHISUPDATE].pbValue, &ftCurrent)) { _PrintError(E_INVALIDARG, "ThisUpdate in future"); } if (0 > CompareFileTime( (FILETIME *) pResult->acol[ICOLRI_CRLNEXTUPDATE].pbValue, &ftCurrent)) { hr = E_INVALIDARG; _JumpError(hr, error, "NextUpdate in past"); }
CSASSERT(0 != pResult->rowid); CSASSERT(NULL == pbCRL); RowId = pResult->rowid; if (NULL != ppbCRL) { cbCRL = pResult->acol[ICOLRI_CRLRAWCRL].cbValue; pbCRL = (BYTE *) LocalAlloc(LMEM_FIXED, cbCRL); if (NULL == pbCRL) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } CopyMemory( pbCRL, pResult->acol[ICOLRI_CRLRAWCRL].pbValue, cbCRL); } if (NULL != pdwCRLPublishFlags && NULL != pResult->acol[ICOLRI_CRLPUBLISHFLAGS].pbValue) { *pdwCRLPublishFlags = *(DWORD *) pResult->acol[ICOLRI_CRLPUBLISHFLAGS].pbValue; } DBGPRINT((DBG_SS_CERTSRVI, "Query:RowId: SAVED %u\n", pResult->rowid));
pView->ReleaseResultRow(celtFetched, aResult); fResultActive = FALSE; } *pdwRowId = RowId; if (NULL != ppbCRL) { *pcbCRL = cbCRL; *ppbCRL = pbCRL; pbCRL = NULL; } hr = S_OK;
error: if (NULL != pbCRL) { LocalFree(pbCRL); } if (NULL != pView) { if (fResultActive) { pView->ReleaseResultRow(celtFetched, aResult); } pView->Release(); } DBGPRINT(( DBG_SS_CERTSRVI, "crlGetRowIdAndCRL(%ws) -> RowId=%u, cbCRL=%x, hr=%x\n", fDelta? L"Delta" : L"Base", *pdwRowId, *pcbCRL, hr)); return(hr); } #undef ICOLRI_CRLNUMBER
#undef ICOLRI_CRLNAMEID
#undef ICOLRI_CRLRAWCRL
#undef ICOLRI_CRLPUBLISHFLAGS
#undef ICOLRI_CRLTHISUPDATEDATE
#undef ICOLRI_CRLNEXTUPDATEDATE
HRESULT crlRepublishCRLFromCAContext( IN FILETIME const *pftCurrent, OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
IN BOOL fDelta, IN CACTX *pCAContext, OUT BOOL *pfRetryNeeded, OUT HRESULT *phrPublish) { HRESULT hr; DWORD cbCRL; BYTE *pbCRL = NULL; DWORD RowId;
*pfRetryNeeded = FALSE; *phrPublish = S_OK;
hr = crlGetRowIdAndCRL(fDelta, pCAContext, &RowId, &cbCRL, &pbCRL, NULL); _JumpIfError(hr, error, "crlGetRowIdAndCRL");
hr = crlPublishGeneratedCRL( RowId, pftCurrent, pwszUserName, fDelta, pCAContext->iKey, pbCRL, cbCRL, pCAContext, pfRetryNeeded, phrPublish); _JumpIfError(hr, error, "crlPublishGeneratedCRL");
error: if (NULL != pbCRL) { LocalFree(pbCRL); } return(hr); }
HRESULT crlRepublishExistingCRLs( OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
IN BOOL fDeltaOnly, IN BOOL fShadowDelta, IN FILETIME const *pftCurrent, OUT BOOL *pfRetryNeeded, OUT HRESULT *phrPublish) { HRESULT hr; HRESULT hrPublish; BOOL fRetryNeeded; DWORD i;
*pfRetryNeeded = FALSE; *phrPublish = S_OK;
// Walk global CA Context array from the back, and republish CRLs for
// each unique CA key. This causes the most current CRL to be published
// first, and the most current CA Cert context to be used to publish a CRL
// that covers multiple CA Certs due to key reuse.
for (i = g_cCACerts; i > 0; i--) { CACTX *pCAContext = &g_aCAContext[i - 1];
PKCSVerifyCAState(pCAContext); if (CTXF_SKIPCRL & pCAContext->Flags) { continue; } if (!fDeltaOnly) { // Publish the most recent existing Base CRL
hr = CertSrvTestServerState(); _JumpIfError(hr, error, "CertSrvTestServerState");
hr = crlRepublishCRLFromCAContext( pftCurrent, pwszUserName, FALSE, // fDelta
pCAContext, &fRetryNeeded, &hrPublish); _JumpIfError(hr, error, "crlRepublishCRLFromCAContext");
if (fRetryNeeded) { *pfRetryNeeded = TRUE; } if (S_OK == *phrPublish) { *phrPublish = hrPublish; } }
if (!g_fDeltaCRLPublishDisabled || fShadowDelta) { // Publish the most recent existing Delta CRL
hr = CertSrvTestServerState(); _JumpIfError(hr, error, "CertSrvTestServerState");
hr = crlRepublishCRLFromCAContext( pftCurrent, pwszUserName, TRUE, // fDelta
pCAContext, &fRetryNeeded, &hrPublish); _JumpIfError(hr, error, "crlRepublishCRLFromCAContext");
if (fRetryNeeded) { *pfRetryNeeded = TRUE; } if (S_OK == *phrPublish) { *phrPublish = hrPublish; } } } hr = S_OK;
error: return(hr); }
HRESULT crlComputeCRLTimes( IN BOOL fDelta, IN CSCRLPERIOD const *pccp, IN FILETIME const *pftCurrent, OUT FILETIME *pftThisUpdate, // ftCurrent - clock skew
IN OUT FILETIME *pftNextUpdate, // ftCurrent + period + overlap + skew
OUT FILETIME *pftNextPublish, // ftCurrent + CRL period
OUT FILETIME *pftPropagationComplete) // ftCurrent + overlap
{ HRESULT hr; LONGLONG lldelta;
if (0 == pftNextUpdate->dwHighDateTime && 0 == pftNextUpdate->dwLowDateTime) { // Calculate expiration date for this CRL:
// ftCurrent + CRL period
DBGPRINTTIME(&fDelta, "*pftCurrent", DPT_DATE, *pftCurrent); *pftNextUpdate = *pftCurrent; DBGPRINT(( DBG_SS_CERTSRVI, "+ count=%d, enum=%d\n", pccp->lCRLPeriodCount, pccp->enumCRLPeriod));
myMakeExprDateTime( pftNextUpdate, pccp->lCRLPeriodCount, pccp->enumCRLPeriod); DBGPRINTTIME(&fDelta, "*pftNextUpdate", DPT_DATE, *pftNextUpdate); } if (0 > CompareFileTime(pftNextUpdate, pftCurrent)) { hr = E_INVALIDARG; _JumpError(hr, error, "*pftNextUpdate in past"); }
*pftThisUpdate = *pftCurrent; *pftNextPublish = *pftNextUpdate; // unmodified expiration time
// Subtract clock skew from the current time for ftThisUpdate time.
lldelta = g_dwClockSkewMinutes * CVT_MINUTES; myAddToFileTime(pftThisUpdate, -lldelta * CVT_BASE);
// Add clock skew to ftNextUpdate,
// Add propogation overlap to ftNextUpdate.
lldelta += pccp->dwCRLOverlapMinutes * CVT_MINUTES; myAddToFileTime(pftNextUpdate, lldelta * CVT_BASE);
*pftPropagationComplete = *pftCurrent; lldelta = pccp->dwCRLOverlapMinutes * CVT_MINUTES; myAddToFileTime(pftPropagationComplete, lldelta * CVT_BASE);
DBGPRINTTIME(&fDelta, "*pftCurrent", DPT_DATE, *pftCurrent); DBGPRINTTIME(&fDelta, "*pftThisUpdate", DPT_DATE, *pftThisUpdate); DBGPRINTTIME(&fDelta, "*pftNextUpdate", DPT_DATE, *pftNextUpdate); DBGPRINTTIME(&fDelta, "*pftNextPublish", DPT_DATE, *pftNextPublish); DBGPRINTTIME(&fDelta, "*pftPropagationComplete", DPT_DATE, *pftPropagationComplete);
hr = S_OK;
error: return(hr); }
HRESULT crlGenerateAndPublishCRLs( OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
IN BOOL fDeltaOnly, // else base (and delta, if enabled)
IN BOOL fShadowDelta, // empty delta CRL with new MinBaseCRL
IN FILETIME const *pftCurrent, IN FILETIME ftNextUpdateBase, OUT DWORD *pdwRowIdBase, OUT FILETIME *pftQueryDeltaDelete, OUT BOOL *pfRetryNeeded, OUT HRESULT *phrPublish) { HRESULT hr; HRESULT hrPublish; HKEY hkeyBase = NULL; HKEY hkeyCA = NULL; BOOL fClamped = FALSE; DWORD CRLNumber; DWORD CRLNumberBaseMin = 0; DWORD i; BOOL fRetryNeeded; FILETIME ftNextUpdateDelta; FILETIME ftThisUpdate; FILETIME ftQueryDelta; FILETIME *pftQueryDelta = &ftQueryDelta; FILETIME ftLastPublishBase; FILETIME ftNextPublishBase; FILETIME ftNextUpdateBaseClamped = ftNextUpdateBase; // if clamped
FILETIME ftNextPublishDelta; FILETIME ftPropagationCompleteBase; FILETIME ftPropagationCompleteDelta; CSCRLPERIOD ccpBase; CSCRLPERIOD ccpDelta;
*pfRetryNeeded = FALSE; pftQueryDeltaDelete->dwHighDateTime = 0; pftQueryDeltaDelete->dwLowDateTime = 0; *phrPublish = S_OK;
hr = crlGetNextCRLNumber(&CRLNumber); _JumpIfError(hr, error, "crlGetNextCRLNumber");
hr = crlGetRegCRLPublishParams( g_wszSanitizedName, &ccpBase, &ccpDelta); _JumpIfError(hr, error, "crlGetRegCRLPublishParams");
// in manual publish case, 0 implies use default publish period
if (fDeltaOnly) { ftNextUpdateDelta = ftNextUpdateBase; ZeroMemory(&ftNextUpdateBase, sizeof(ftNextUpdateBase)); } else { ZeroMemory(&ftNextUpdateDelta, sizeof(ftNextUpdateDelta)); }
hr = crlComputeCRLTimes( FALSE, // fDelta
&ccpBase, // IN
pftCurrent, // IN
&ftThisUpdate, // OUT includes skew
&ftNextUpdateBase, // INOUT includes overlap, skew
&ftNextPublishBase, // OUT unmodified expire time
&ftPropagationCompleteBase); // OUT includes overlap
_JumpIfError(hr, error, "crlComputeCRLTimes");
hr = crlComputeCRLTimes( TRUE, // fDelta
fShadowDelta? &ccpBase : &ccpDelta, // IN
pftCurrent, // IN
&ftThisUpdate, // OUT includes skew
&ftNextUpdateDelta, // INOUT includes overlap, skew
&ftNextPublishDelta, // OUT unmodified expire time
&ftPropagationCompleteDelta); // OUT includes overlap
_JumpIfError(hr, error, "crlComputeCRLTimes");
// Set ftLastPublishBase to *pftCurrent minus lifetime of this base CRL,
// which is an educated guess for the ftThisPublish value for the last
// CRL issued.
ftLastPublishBase = *pftCurrent; myAddToFileTime( &ftLastPublishBase, -mySubtractFileTimes(&ftNextPublishBase, pftCurrent));
// Clamp delta CRL to not end after base CRL.
if (0 < CompareFileTime(&ftNextPublishDelta, &ftNextPublishBase)) { ftNextPublishDelta = ftNextPublishBase; DBGPRINTTIME(NULL, "ftNextPublishDelta", DPT_DATE, ftNextPublishDelta); } if (0 < CompareFileTime(&ftNextUpdateDelta, &ftNextUpdateBase)) { ftNextUpdateDelta = ftNextUpdateBase; DBGPRINTTIME(NULL, "ftNextUpdateDelta", DPT_DATE, ftNextUpdateDelta); } if (0 < CompareFileTime(&ftPropagationCompleteDelta, &ftPropagationCompleteBase)) { ftPropagationCompleteDelta = ftPropagationCompleteBase; DBGPRINTTIME(NULL, "ftPropagationCompleteDelta", DPT_DATE, ftPropagationCompleteDelta); } if (!g_fDeltaCRLPublishDisabled || fShadowDelta) { hr = crlGetBaseCRLInfo( pftCurrent, FALSE, // try newest propagated CRL
pdwRowIdBase, &CRLNumberBaseMin, &ftQueryDelta); _PrintIfError(hr, "crlGetBaseCRLInfo"); if (S_OK != hr) { hr = crlGetBaseCRLInfo( pftCurrent, TRUE, // try oldest unexpired CRL
pdwRowIdBase, &CRLNumberBaseMin, &ftQueryDelta); _PrintIfError(hr, "crlGetBaseCRLInfo"); if (S_OK != hr) { CRLNumberBaseMin = 1; if (!fDeltaOnly && 1 == CRLNumber) { ftQueryDelta = *pftCurrent; // empty CRL
} else { pftQueryDelta = NULL; // full CRL
} } } if (S_OK == hr) { // Delete old CRLs that expired at least one base CRL period prior
// to the "minimum" base crl ThisUpdate date found in the database.
*pftQueryDeltaDelete = ftQueryDelta; myAddToFileTime( pftQueryDeltaDelete, -mySubtractFileTimes(&ftNextUpdateBase, &ftThisUpdate)); } if (fShadowDelta) { CRLNumberBaseMin = CRLNumber; } CSASSERT(0 != CRLNumberBaseMin); }
// Walk global CA Context array from the back, and generate a CRL for
// each unique CA key. This causes the most current CRL to be built
// first, and the most current CA Cert to be used to build a CRL that
// covers multiple CA Certs due to key reuse.
for (i = g_cCACerts; i > 0; i--) { CACTX *pCAContext = &g_aCAContext[i - 1];
PKCSVerifyCAState(pCAContext); if (CTXF_SKIPCRL & pCAContext->Flags) { continue; } if (!fDeltaOnly) { // Publish a new Base CRL
// make a local copy in case clamped
FILETIME ftNextUpdateBaseTemp = ftNextUpdateBase; fClamped = FALSE;
hr = CertSrvTestServerState(); _JumpIfError(hr, error, "CertSrvTestServerState");
hr = crlPublishCRLFromCAContext( CRLNumber, 0, // CRLNumberBaseMin
pwszUserName, FALSE, // fShadowDelta
pCAContext, pftCurrent, ftThisUpdate, &ftNextUpdateBaseTemp, &fClamped, NULL, pftCurrent, &ftNextPublishBase, &ftLastPublishBase, &ftPropagationCompleteBase, &fRetryNeeded, &hrPublish); _JumpIfError(hr, error, "crlPublishCRLFromCAContext");
if (fRetryNeeded) { *pfRetryNeeded = TRUE; } if (S_OK == *phrPublish) { *phrPublish = hrPublish; }
{ CertSrv::CAuditEvent event(SE_AUDITID_CERTSRV_AUTOPUBLISHCRL, g_dwAuditFilter);
hr = event.AddData(true); // %1 base crl?
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.AddData(CRLNumber); // %2 CRL#
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.AddData(pCAContext->pwszKeyContainerName); // %3 key container
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.AddData(ftNextPublishBase); // %4 next publish
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.AddData((LPCWSTR*)pCAContext->papwszCRLFiles); //%5 URLs
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.Report(); _JumpIfError(hr, error, "CAuditEvent::Report"); } if (i == g_cCACerts && fClamped) { // new next publish clamps with CA expiration, only update
// the current crl with new one for later reg save
ftNextUpdateBaseClamped = ftNextUpdateBaseTemp; } }
if (!g_fDeltaCRLPublishDisabled || fShadowDelta) { // Publish a new Delta CRL
FILETIME ftNextUpdateDeltaTemp = ftNextUpdateDelta;
hr = CertSrvTestServerState(); _JumpIfError(hr, error, "CertSrvTestServerState");
hr = crlPublishCRLFromCAContext( CRLNumber, CRLNumberBaseMin, pwszUserName, fShadowDelta, pCAContext, pftCurrent, ftThisUpdate, &ftNextUpdateDeltaTemp, NULL, pftQueryDelta, pftCurrent, &ftNextPublishDelta, &ftLastPublishBase, // Base!
&ftPropagationCompleteDelta, &fRetryNeeded, &hrPublish); _JumpIfError(hr, error, "crlPublishCRLFromCAContext");
if (fRetryNeeded) { *pfRetryNeeded = TRUE; } if (S_OK == *phrPublish) { *phrPublish = hrPublish; }
{ CertSrv::CAuditEvent event(SE_AUDITID_CERTSRV_AUTOPUBLISHCRL, g_dwAuditFilter);
hr = event.AddData(false); // %1 base crl?
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.AddData(CRLNumber); // %2 CRL#
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.AddData(pCAContext->pwszKeyContainerName); // %3 key container
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.AddData(ftNextPublishDelta); // %4 next publish
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.AddData((LPCWSTR*)pCAContext->papwszDeltaCRLFiles); // %5 URLs
_JumpIfError(hr, error, "CAuditEvent::AddData");
hr = event.Report(); _JumpIfError(hr, error, "CAuditEvent::Report"); } } }
// update the registry and global variables
if (!fDeltaOnly) { if (!fClamped) { g_ftCRLNextPublish = ftNextPublishBase; } else { g_ftCRLNextPublish = ftNextUpdateBaseClamped; } hr = crlSetRegCRLNextPublish( FALSE, g_wszSanitizedName, wszREGCRLNEXTPUBLISH, &g_ftCRLNextPublish); _JumpIfError(hr, error, "crlSetRegCRLNextPublish"); }
g_ftDeltaCRLNextPublish = ftNextPublishDelta;
if (!g_fDeltaCRLPublishDisabled) { hr = crlSetRegCRLNextPublish( TRUE, g_wszSanitizedName, wszREGCRLDELTANEXTPUBLISH, &g_ftDeltaCRLNextPublish); _JumpIfError(hr, error, "crlSetRegCRLNextPublish"); } hr = S_OK;
error: if (NULL != hkeyCA) { RegCloseKey(hkeyCA); } if (NULL != hkeyBase) { RegCloseKey(hkeyBase); } return(hr); }
///////////////////////////////////////////////////
// CRLPublishCRLs is called to publish a set of CRLs.
//
// if fRebuildCRL is TRUE, the CRLs are rebuilt from the database.
// otherwise, the exit module is re-notified of the CRLs.
// For consistency, if the exit module returns ERROR_RETRY, this
// function will write the retry bit into the registry which will
// trigger the Wakeup function, which then recalculates when the
// next publish should happen.
//
// pfRetryNeeded is an OUT param that notifies the autopublish routine if
// a retry is immediately necessary following a rebuilt CRL. In this
// case the registry would not be changed and the registry trigger
// would not fire.
//
// (Current_time - skew) is used as ThisUpdate
// (ftNextUpdate+skew+Overlap) is used as NextUpdate
// (ftNextUpdate) is next wakeup/publish time
//
// There are registry values to specify the overlap.
// HLKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\<CA Name>:
// CRLOverlapPeriod REG_SZ = Hours (or Minutes)
// CRLOverlapUnits REG_DWORD = 0 (0) -- DISABLED
//
// If the above registry values are set and valid, the registry overlap period
// is calculated as:
// max(Registry CRL Overlap Period, 1.5 * Registry clock skew minutes)
//
// If they are not present or invalid, the overlap period is calculated as:
// max(
// min(Registry CRL Period / 10, 12 hours),
// 1.5 * Registry clock skew minutes) +
// Registry clock skew minutes
//
// ThisUpdate is calculated as:
// max(Current Time - Registry clock skew minutes, CA cert NotBefore date)
//
// NextUpdate is calculated as:
// min(
// Current Time +
// Registry CRL period +
// calculated overlap period +
// Registry clock skew minutes,
// CA cert NotAfter date)
//
// The Next CRL publication time is calculated as:
// Current Time + Registry CRL period
//
// This function sets g_hCRLManualPublishEvent. Automatic publishing
// is personally responsible for clearing this event if it calls us.
HRESULT CRLPublishCRLs( IN BOOL fRebuildCRL, // else republish only
IN BOOL fForceRepublish, // else check registry retry count
OPTIONAL IN WCHAR const *pwszUserName, // else timer thread
IN BOOL fDeltaOnly, // else base (and delta, if enabled)
IN BOOL fShadowDelta, // empty delta CRL with new MinBaseCRL
IN FILETIME ftNextUpdateBase, OUT BOOL *pfRetryNeeded, OUT HRESULT *phrPublish) { HRESULT hr; BOOL fRetryNeeded = FALSE; BOOL fExitNotify = FALSE; BOOL fCoInitialized = FALSE; DWORD RowIdBase = 0; FILETIME ftQueryDeltaDelete = { 0, 0 }; DWORD dwPreviousAttempts; DWORD dwCurrentAttempts; static BOOL s_fSkipRetry = FALSE;
*pfRetryNeeded = FALSE; *phrPublish = S_OK;
if (fDeltaOnly && g_fDeltaCRLPublishDisabled && !fShadowDelta) { hr = HRESULT_FROM_WIN32(ERROR_RESOURCE_DISABLED); _JumpError(hr, error, "g_fDeltaCRLPublishDisabled"); }
// retrieve initial retry value (optional registry value)
hr = myGetCertRegDWValue( g_wszSanitizedName, NULL, NULL, wszREGCRLATTEMPTREPUBLISH, &dwPreviousAttempts); if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) { dwPreviousAttempts = 0; // assume no previous failed publish attempts
hr = S_OK; } _JumpIfErrorStr( hr, error, "myGetCertRegDWValue", wszREGCRLATTEMPTREPUBLISH);
dwCurrentAttempts = dwPreviousAttempts; DBGPRINT(( DBG_SS_CERTSRV, "CRLPublishCRLs(fRebuildCRL=%u, fForceRepublish=%u, User=%ws)\n", fRebuildCRL, fForceRepublish, pwszUserName)); DBGPRINT(( DBG_SS_CERTSRV, "CRLPublishCRLs(fDeltaOnly=%u, fShadowDelta=%u, dwPreviousAttempts=%u)\n", fDeltaOnly, fShadowDelta, dwPreviousAttempts));
if (0 != dwPreviousAttempts && NULL == pwszUserName && s_fSkipRetry) { fRetryNeeded = TRUE; } else { FILETIME ftCurrent;
GetSystemTimeAsFileTime(&ftCurrent);
// generate CRLs if necessary
if (fRebuildCRL) { hr = crlGenerateAndPublishCRLs( pwszUserName, fDeltaOnly, fShadowDelta, &ftCurrent, ftNextUpdateBase, &RowIdBase, &ftQueryDeltaDelete, &fRetryNeeded, phrPublish); _JumpIfError(hr, error, "crlGenerateAndPublishCRLs");
fExitNotify = TRUE; dwCurrentAttempts = 1; } else if (fForceRepublish || (0 < dwPreviousAttempts && CERTSRV_CRLPUB_RETRY_COUNT_DEFAULT > dwPreviousAttempts)) { // If the timer thread is auto-republishing due to previously
// failed publish attempts, retry base CRLs, too, because we
// can't tell if the retry is due to a base or delta CRL error.
if (NULL == pwszUserName) { fDeltaOnly = FALSE; }
hr = crlRepublishExistingCRLs( pwszUserName, fDeltaOnly, fShadowDelta, &ftCurrent, &fRetryNeeded, phrPublish); _JumpIfError(hr, error, "crlRepublishCRLs");
fExitNotify = TRUE; dwCurrentAttempts++; }
if (fExitNotify && g_fEnableExit) { hr = CoInitializeEx(NULL, GetCertsrvComThreadingModel()); if (S_OK != hr && S_FALSE != hr) { _JumpError(hr, error, "CoInitializeEx"); } fCoInitialized = TRUE;
// make sure exit module(s) get notified for publish and republish
// in case of earlier exit module publish failure.
hr = ExitNotify(EXITEVENT_CRLISSUED, 0, MAXDWORD); _PrintIfError(hr, "ExitNotify"); if ((HRESULT) ERROR_RETRY == hr || HRESULT_FROM_WIN32(ERROR_RETRY) == hr) { fRetryNeeded = TRUE; if (S_OK == *phrPublish) { *phrPublish = HRESULT_FROM_WIN32(ERROR_RETRY); } } CONSOLEPRINT0((DBG_SS_CERTSRV, "Issued CRL Exit Event\n")); }
// If new or existing CRLs successfully published, reset count to 0
if (fExitNotify && !fRetryNeeded) { dwCurrentAttempts = 0; if (CERTLOG_VERBOSE <= g_dwLogLevel) { WCHAR *pwszHostName = NULL; DWORD LogMsg; WORD cpwsz = 0;
if (NULL != g_pld) { myLdapGetDSHostName(g_pld, &pwszHostName); } LogMsg = fDeltaOnly? MSG_DELTA_CRLS_PUBLISHED : (g_fDeltaCRLPublishDisabled? MSG_BASE_CRLS_PUBLISHED : MSG_BASE_AND_DELTA_CRLS_PUBLISHED); if (NULL != pwszHostName) { LogMsg = fDeltaOnly? MSG_DELTA_CRLS_PUBLISHED_HOST_NAME : (g_fDeltaCRLPublishDisabled? MSG_BASE_CRLS_PUBLISHED_HOST_NAME : MSG_BASE_AND_DELTA_CRLS_PUBLISHED_HOST_NAME); } hr = LogEvent( EVENTLOG_INFORMATION_TYPE, LogMsg, NULL == pwszHostName? 0 : 1, // cStrings
(WCHAR const **) &pwszHostName); // apwszStrings
_PrintIfError(hr, "LogEvent"); } }
// If the retry count has changed, update the registry.
if (dwCurrentAttempts != dwPreviousAttempts) { DBGPRINT(( DBG_SS_CERTSRV, "CRLPublishCRLs(Attempts: %u --> %u)\n", dwPreviousAttempts, dwCurrentAttempts));
hr = mySetCertRegDWValue( g_wszSanitizedName, NULL, NULL, wszREGCRLATTEMPTREPUBLISH, dwCurrentAttempts); _JumpIfErrorStr( hr, error, "mySetCertRegDWValue", wszREGCRLATTEMPTREPUBLISH);
// If we tried unsuccessfully too many times to publish these CRLs,
// and we're about to give up until a new set is generated, log an
// event saying so.
if (fExitNotify && CERTSRV_CRLPUB_RETRY_COUNT_DEFAULT == dwCurrentAttempts && CERTLOG_ERROR <= g_dwLogLevel) { WCHAR wszAttempts[11 + 1]; WCHAR const *pwsz = wszAttempts;
wsprintf(wszAttempts, L"%u", dwCurrentAttempts); hr = LogEvent( EVENTLOG_ERROR_TYPE, MSG_E_CRL_PUBLICATION_TOO_MANY_RETRIES, 1, // cStrings
&pwsz); // apwszStrings
_PrintIfError(hr, "LogEvent"); } } if (fRebuildCRL) { // Delete old CRLs only if new CRLs built & published successfully.
if (!fRetryNeeded) { hr = CertSrvTestServerState(); _JumpIfError(hr, error, "CertSrvTestServerState");
hr = crlDeleteExpiredCRLs( &ftCurrent, &ftQueryDeltaDelete, RowIdBase); _PrintIfError(hr, "crlDeleteExpiredCRLs"); }
// Clear force CRL flag only when we build new CRLs.
hr = SetSetupStatus(g_wszSanitizedName, SETUP_FORCECRL_FLAG, FALSE); _PrintIfError(hr, "SetSetupStatus"); } } s_fSkipRetry = NULL != pwszUserName;
if (fRebuildCRL || fRetryNeeded) { // If we are doing ANYTHING that will affect automatic wakeup, trigger
// our publish event.
// NOTE: do this last or else state might not be updated
SetEvent(g_hCRLManualPublishEvent); } hr = S_OK;
error: *pfRetryNeeded = fRetryNeeded; if (fCoInitialized) { CoUninitialize(); } return(hr); }
HRESULT CRLGetCRL( IN DWORD iCertArg, IN BOOL fDelta, OPTIONAL OUT CRL_CONTEXT const **ppCRL, OPTIONAL OUT DWORD *pdwCRLPublishFlags) { HRESULT hr; DWORD State; DWORD iCert; DWORD iCRL; CACTX *pCAContext; DWORD dwRowId; BYTE *pbCRL = NULL; DWORD cbCRL;
if (NULL != ppCRL) { *ppCRL = NULL; }
hr = PKCSMapCRLIndex(iCertArg, &iCert, &iCRL, &State); _JumpIfError(hr, error, "PKCSMapCRLIndex");
if (MAXDWORD != iCertArg && CA_DISP_VALID != State) { hr = E_INVALIDARG; _JumpError(hr, error, "No CRL for this Cert"); }
// Now we know iCert is a valid Cert Index:
hr = crlGetRowIdAndCRL( fDelta, &g_aCAContext[iCert], &dwRowId, &cbCRL, &pbCRL, pdwCRLPublishFlags); _JumpIfError(hr, error, "crlGetRowIdAndCRL");
if (NULL != ppCRL) { *ppCRL = CertCreateCRLContext(X509_ASN_ENCODING, pbCRL, cbCRL); if (NULL == *ppCRL) { hr = myHLastError(); _JumpError(hr, error, "CertCreateCRLContext"); } } hr = S_OK;
error: if (NULL != pbCRL) { LocalFree(pbCRL); } return(hr); }
|