//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1995 - 1999
//
//  File:       revfunc.cpp
//
//  Contents:   Certificate Revocation Dispatch Functions
//
//  Functions:  I_CertRevFuncDllMain
//              CertVerifyRevocation
//
//  History:    12-Dec-96    philh   created
//              11-Mar-97    philh   changed signature of CertVerifyRevocation
//--------------------------------------------------------------------------

#include "global.hxx"
#include <dbgdef.h>

static HCRYPTOIDFUNCSET hRevFuncSet;

typedef BOOL (WINAPI *PFN_CERT_DLL_VERIFY_REVOCATION)(
    IN DWORD dwEncodingType,
    IN DWORD dwRevType,
    IN DWORD cContext,
    IN PVOID rgpvContext[],
    IN DWORD dwFlags,
    IN OPTIONAL PCERT_REVOCATION_PARA pRevPara,
    IN OUT PCERT_REVOCATION_STATUS pRevStatus
    );

//+-------------------------------------------------------------------------
//  Dll initialization
//--------------------------------------------------------------------------
BOOL
WINAPI
I_CertRevFuncDllMain(
        HMODULE hModule,
        ULONG  ulReason,
        LPVOID lpReserved)
{
    BOOL    fRet;

    switch (ulReason) {
    case DLL_PROCESS_ATTACH:
        if (NULL == (hRevFuncSet = CryptInitOIDFunctionSet(
                CRYPT_OID_VERIFY_REVOCATION_FUNC,
                0)))                                // dwFlags
            goto CryptInitOIDFunctionSetError;
        break;

    case DLL_PROCESS_DETACH:
    case DLL_THREAD_DETACH:
    default:
        break;
    }

    fRet = TRUE;
CommonReturn:
    return fRet;

ErrorReturn:
    fRet = FALSE;
    goto CommonReturn;
TRACE_ERROR(CryptInitOIDFunctionSetError)
}

static inline void ZeroRevStatus(OUT PCERT_REVOCATION_STATUS pRevStatus)
{
    DWORD cbSize = pRevStatus->cbSize;
    
    memset(pRevStatus, 0, cbSize);
    pRevStatus->cbSize = cbSize;
}

// Remember the first "interesting" error. *pdwError is initialized to
// CRYPT_E_NO_REVOCATION_DLL.
static void UpdateNoRevocationCheckStatus(
    IN PCERT_REVOCATION_STATUS pRevStatus,
    IN OUT DWORD *pdwError,
    IN OUT DWORD *pdwReason,
    IN OUT BOOL *pfHasFreshnessTime,
    IN OUT DWORD *pdwFreshnessTime
    )
{
    if (pRevStatus->dwError &&
            (*pdwError == (DWORD) CRYPT_E_NO_REVOCATION_DLL ||
                *pdwError == (DWORD) CRYPT_E_NO_REVOCATION_CHECK)) {
        *pdwError = pRevStatus->dwError;
        *pdwReason = pRevStatus->dwReason;

        if (pRevStatus->cbSize >= STRUCT_CBSIZE(CERT_REVOCATION_STATUS,
                dwFreshnessTime)) {
            *pfHasFreshnessTime = pRevStatus->fHasFreshnessTime;
            *pdwFreshnessTime = pRevStatus->dwFreshnessTime;
        }
    }
}

static BOOL VerifyDefaultRevocation(
    IN DWORD dwEncodingType,
    IN DWORD dwRevType,
    IN DWORD cContext,
    IN PVOID rgpvContext[],
    IN DWORD dwFlags,
    IN FILETIME *pftEndUrlRetrieval,
    IN OPTIONAL PCERT_REVOCATION_PARA pRevPara,
    IN OUT PCERT_REVOCATION_STATUS pRevStatus
    )
{
    BOOL fResult;
    DWORD dwError = (DWORD) CRYPT_E_NO_REVOCATION_DLL;
    DWORD dwReason = 0;
    BOOL fHasFreshnessTime = FALSE;
    DWORD dwFreshnessTime = 0;
    LPWSTR pwszDllList;       // _alloca'ed
    DWORD cchDllList;
    DWORD cchDll;
    void *pvFuncAddr;
    HCRYPTOIDFUNCADDR hFuncAddr;

    // Iterate through the installed default functions.
    // Setting pwszDll to NULL searches the installed list. Setting
    // hFuncAddr to NULL starts the search at the beginning.
    hFuncAddr = NULL;
    while (CryptGetDefaultOIDFunctionAddress(
                hRevFuncSet,
                dwEncodingType,
                NULL,               // pwszDll
                0,                  // dwFlags
                &pvFuncAddr,
                &hFuncAddr)) {
        ZeroRevStatus(pRevStatus);

        if (dwFlags & CERT_VERIFY_REV_ACCUMULATIVE_TIMEOUT_FLAG) {
            pRevPara->dwUrlRetrievalTimeout =
                I_CryptRemainingMilliseconds(pftEndUrlRetrieval);
            if (0 == pRevPara->dwUrlRetrievalTimeout)
                pRevPara->dwUrlRetrievalTimeout = 1;
        }

        fResult = ((PFN_CERT_DLL_VERIFY_REVOCATION) pvFuncAddr)(
                dwEncodingType,
                dwRevType,
                cContext,
                rgpvContext,
                dwFlags,
                pRevPara,
                pRevStatus);
        if (fResult || CRYPT_E_REVOKED == pRevStatus->dwError ||
                0 < pRevStatus->dwIndex) {
            // All contexts successfully checked, one of the contexts
            // was revoked or successfully able to check at least one
            // of the contexts.
            CryptFreeOIDFunctionAddress(hFuncAddr, 0);
            goto CommonReturn;
        } else
            // Unable to check revocation for this installed
            // function. However, remember any "interesting"
            // errors such as, offline.
            UpdateNoRevocationCheckStatus(pRevStatus, &dwError, &dwReason,
                &fHasFreshnessTime, &dwFreshnessTime);
    }

    if (!CryptGetDefaultOIDDllList(
            hRevFuncSet,
            dwEncodingType,
            NULL,               // pszDllList
            &cchDllList)) goto GetDllListError;
    __try {
        pwszDllList = (LPWSTR) _alloca(cchDllList * sizeof(WCHAR));
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        goto OutOfMemory;
    }
    if (!CryptGetDefaultOIDDllList(
            hRevFuncSet,
            dwEncodingType,
            pwszDllList,
            &cchDllList)) goto GetDllListError;

    for (; 0 != (cchDll = wcslen(pwszDllList)); pwszDllList += cchDll + 1) {
        if (CryptGetDefaultOIDFunctionAddress(
                hRevFuncSet,
                dwEncodingType,
                pwszDllList,
                0,              // dwFlags
                &pvFuncAddr,
                &hFuncAddr)) {
            ZeroRevStatus(pRevStatus);

            if (dwFlags & CERT_VERIFY_REV_ACCUMULATIVE_TIMEOUT_FLAG) {
                pRevPara->dwUrlRetrievalTimeout =
                    I_CryptRemainingMilliseconds(pftEndUrlRetrieval);
                if (0 == pRevPara->dwUrlRetrievalTimeout)
                    pRevPara->dwUrlRetrievalTimeout = 1;
            }

            fResult = ((PFN_CERT_DLL_VERIFY_REVOCATION) pvFuncAddr)(
                    dwEncodingType,
                    dwRevType,
                    cContext,
                    rgpvContext,
                    dwFlags,
                    pRevPara,
                    pRevStatus);
            CryptFreeOIDFunctionAddress(hFuncAddr, 0);
            if (fResult || CRYPT_E_REVOKED == pRevStatus->dwError ||
                    0 < pRevStatus->dwIndex)
                // All contexts successfully checked, one of the contexts
                // was revoked or successfully able to check at least one
                // of the contexts.
                goto CommonReturn;
            else
                // Unable to check revocation for this registered
                // function. However, remember any "interesting"
                // errors such as, offline.
                UpdateNoRevocationCheckStatus(pRevStatus, &dwError, &dwReason,
                    &fHasFreshnessTime, &dwFreshnessTime);
        }
    }

    goto ErrorReturn;

CommonReturn:
    return fResult;
ErrorReturn:
    pRevStatus->dwIndex = 0;
    pRevStatus->dwError = dwError;
    pRevStatus->dwReason = dwReason;

    if (pRevStatus->cbSize >= STRUCT_CBSIZE(CERT_REVOCATION_STATUS,
            dwFreshnessTime)) {
        pRevStatus->fHasFreshnessTime = fHasFreshnessTime;
        pRevStatus->dwFreshnessTime = dwFreshnessTime;
    }

    fResult = FALSE;
    goto CommonReturn;
TRACE_ERROR(GetDllListError)
TRACE_ERROR(OutOfMemory)
}

//+-------------------------------------------------------------------------
//  Verifies the array of contexts for revocation. The dwRevType parameter
//  indicates the type of the context data structure passed in rgpvContext.
//  Currently only the revocation of certificates is defined.
//
//  If the CERT_VERIFY_REV_CHAIN_FLAG flag is set, then, CertVerifyRevocation
//  is verifying a chain of certs where, rgpvContext[i + 1] is the issuer
//  of rgpvContext[i]. Otherwise, CertVerifyRevocation makes no assumptions
//  about the order of the contexts.
//
//  To assist in finding the issuer, the pRevPara may optionally be set. See
//  the CERT_REVOCATION_PARA data structure for details.
//
//  The contexts must contain enough information to allow the
//  installable or registered revocation DLLs to find the revocation server. For
//  certificates, this information would normally be conveyed in an
//  extension such as the IETF's AuthorityInfoAccess extension.
//
//  CertVerifyRevocation returns TRUE if all of the contexts were successfully
//  checked and none were revoked. Otherwise, returns FALSE and updates the
//  returned pRevStatus data structure as follows:
//    dwIndex
//      Index of the first context that was revoked or unable to
//      be checked for revocation
//    dwError
//      Error status. LastError is also set to this error status.
//      dwError can be set to one of the following error codes defined
//      in winerror.h:
//        ERROR_SUCCESS - good context
//        CRYPT_E_REVOKED - context was revoked. dwReason contains the
//           reason for revocation
//        CRYPT_E_REVOCATION_OFFLINE - unable to connect to the
//           revocation server
//        CRYPT_E_NOT_IN_REVOCATION_DATABASE - the context to be checked
//           was not found in the revocation server's database.
//        CRYPT_E_NO_REVOCATION_CHECK - the called revocation function
//           wasn't able to do a revocation check on the context
//        CRYPT_E_NO_REVOCATION_DLL - no installed or registered Dll was
//           found to verify revocation
//    dwReason
//      The dwReason is currently only set for CRYPT_E_REVOKED and contains
//      the reason why the context was revoked. May be one of the following
//      CRL reasons defined by the CRL Reason Code extension ("2.5.29.21")
//          CRL_REASON_UNSPECIFIED              0
//          CRL_REASON_KEY_COMPROMISE           1
//          CRL_REASON_CA_COMPROMISE            2
//          CRL_REASON_AFFILIATION_CHANGED      3
//          CRL_REASON_SUPERSEDED               4
//          CRL_REASON_CESSATION_OF_OPERATION   5
//          CRL_REASON_CERTIFICATE_HOLD         6
//
//  For each entry in rgpvContext, CertVerifyRevocation iterates
//  through the CRYPT_OID_VERIFY_REVOCATION_FUNC
//  function set's list of installed DEFAULT functions.
//  CryptGetDefaultOIDFunctionAddress is called with pwszDll = NULL. If no
//  installed functions are found capable of doing the revocation verification,
//  CryptVerifyRevocation iterates through CRYPT_OID_VERIFY_REVOCATION_FUNC's
//  list of registered DEFAULT Dlls. CryptGetDefaultOIDDllList is called to
//  get the list. CryptGetDefaultOIDFunctionAddress is called to load the Dll.
//
//  The called functions have the same signature as CertVerifyRevocation. A
//  called function returns TRUE if it was able to successfully check all of
//  the contexts and none were revoked. Otherwise, the called function returns
//  FALSE and updates pRevStatus. dwIndex is set to the index of
//  the first context that was found to be revoked or unable to be checked.
//  dwError and LastError are updated. For CRYPT_E_REVOKED, dwReason
//  is updated. Upon input to the called function, dwIndex, dwError and
//  dwReason have been zero'ed. cbSize has been checked to be >=
//  sizeof(CERT_REVOCATION_STATUS).
//  
//  If the called function returns FALSE, and dwError isn't set to
//  CRYPT_E_REVOKED, then, CertVerifyRevocation either continues on to the
//  next DLL in the list for a returned dwIndex of 0 or for a returned
//  dwIndex > 0, restarts the process of finding a verify function by
//  advancing the start of the context array to the returned dwIndex and
//  decrementing the count of remaining contexts.
//--------------------------------------------------------------------------
BOOL
WINAPI
CertVerifyRevocation(
    IN DWORD dwEncodingType,
    IN DWORD dwRevType,
    IN DWORD cContext,
    IN PVOID rgpvContext[],
    IN DWORD dwFlags,
    IN OPTIONAL PCERT_REVOCATION_PARA pRevPara,
    IN OUT PCERT_REVOCATION_STATUS pRevStatus
    )
{
    BOOL fResult = FALSE;
    DWORD dwIndex;

    // Following are only used for CERT_VERIFY_REV_ACCUMULATIVE_TIMEOUT_FLAG
    CERT_REVOCATION_PARA RevPara;
    FILETIME ftEndUrlRetrieval;

    assert(pRevStatus->cbSize >= STRUCT_CBSIZE(CERT_REVOCATION_STATUS,
            dwReason));
    if (pRevStatus->cbSize < STRUCT_CBSIZE(CERT_REVOCATION_STATUS,
            dwReason))
        goto InvalidArg;

    if (dwFlags & CERT_VERIFY_REV_ACCUMULATIVE_TIMEOUT_FLAG) {
        // RevPara.dwUrlRetrievalTimeout will be updated with the remaining
        // timeout

        memset(&RevPara, 0, sizeof(RevPara));
        if (pRevPara != NULL)
            memcpy(&RevPara, pRevPara, min(pRevPara->cbSize, sizeof(RevPara)));
        RevPara.cbSize = sizeof(RevPara);
        if (0 == RevPara.dwUrlRetrievalTimeout)
            dwFlags &= ~CERT_VERIFY_REV_ACCUMULATIVE_TIMEOUT_FLAG;
        else {
            FILETIME ftCurrent;

            GetSystemTimeAsFileTime(&ftCurrent);
            I_CryptIncrementFileTimeByMilliseconds(
                &ftCurrent, RevPara.dwUrlRetrievalTimeout, &ftEndUrlRetrieval);

            pRevPara = &RevPara;
        }
    }

    dwIndex = 0;
    while (dwIndex < cContext) {
        fResult = VerifyDefaultRevocation(
                dwEncodingType,
                dwRevType,
                cContext - dwIndex,
                &rgpvContext[dwIndex],
                dwFlags,
                &ftEndUrlRetrieval,
                pRevPara,
                pRevStatus
                );
        if (fResult)
            // All contexts successfully checked.
            break;
        else if (CRYPT_E_REVOKED == pRevStatus->dwError ||
                0 == pRevStatus->dwIndex) {
            // One of the contexts was revoked or unable to check the
            // dwIndex context.
            pRevStatus->dwIndex += dwIndex;
            SetLastError(pRevStatus->dwError);
            break;
        } else
            // Advance past the checked contexts
            dwIndex += pRevStatus->dwIndex;
    }

    if (dwIndex >= cContext) {
        // Able to check all the contexts
        fResult = TRUE;
        pRevStatus->dwIndex = 0;
        pRevStatus->dwError = 0;
        pRevStatus->dwReason = 0;
    }

CommonReturn:
    return fResult;
ErrorReturn:
    fResult = FALSE;
    goto CommonReturn;

SET_ERROR(InvalidArg, E_INVALIDARG)
}