//+-------------------------------------------------------------------------
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 2001 - 2001
//
//  File:	    mscrlrev.cpp
//
//  Contents:   Check CRLs in CA store version of CertDllVerifyRevocation.
//
//              Restrictions:
//               - Only support CRYPT_ASN_ENCODING
//               - CRL must already be in the CA system store
//               - CRL must be issued and signed by the issuer of the
//                 certificate
//               - CRL must not have any critical extensions
//
//  Functions:  DllMain
//              DllRegisterServer
//              DllUnregisterServer
//              CertDllVerifyRevocation
//
//  History:	15-Mar-01	philh   created
//--------------------------------------------------------------------------


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


//+-------------------------------------------------------------------------
// Default stores searched to find an issuer of the subject certificate
//--------------------------------------------------------------------------
struct {
    LPCWSTR     pwszStore;
    DWORD       dwFlags;
} rgDefaultIssuerStores[] = {
    L"CA",          CERT_SYSTEM_STORE_CURRENT_USER,
    L"ROOT",        CERT_SYSTEM_STORE_CURRENT_USER
};
#define NUM_DEFAULT_ISSUER_STORES (sizeof(rgDefaultIssuerStores) / \
                                    sizeof(rgDefaultIssuerStores[0]))

//+-------------------------------------------------------------------------
//  Dll initialization
//--------------------------------------------------------------------------
BOOL
WINAPI
DllMain(
    HMODULE hInst,
    ULONG  ulReason,
    LPVOID lpReserved)
{
    switch (ulReason) {
    case DLL_PROCESS_ATTACH:
        break;
    case DLL_PROCESS_DETACH:
    case DLL_THREAD_DETACH:
    default:
        break;
    }

    return TRUE;
}

HRESULT HError()
{
    DWORD dw = GetLastError();

    HRESULT hr;
    if ( dw <= 0xFFFF )
        hr = HRESULT_FROM_WIN32 ( dw );
    else
        hr = dw;

    if ( ! FAILED ( hr ) )
    {
        // somebody failed a call without properly setting an error condition

        hr = E_UNEXPECTED;
    }
    return hr;
}

//+-------------------------------------------------------------------------
//  DllRegisterServer
//--------------------------------------------------------------------------
STDAPI DllRegisterServer(void)
{
    if (!CryptRegisterDefaultOIDFunction(
            X509_ASN_ENCODING,
            CRYPT_OID_VERIFY_REVOCATION_FUNC,
            CRYPT_REGISTER_FIRST_INDEX,
            L"mscrlrev.dll"
            )) {
        if (ERROR_FILE_EXISTS != GetLastError())
            return HError();
    }

    return S_OK;
}

//+-------------------------------------------------------------------------
//  DllUnregisterServer
//--------------------------------------------------------------------------
STDAPI DllUnregisterServer(void)
{
    if (!CryptUnregisterDefaultOIDFunction(
            X509_ASN_ENCODING,
            CRYPT_OID_VERIFY_REVOCATION_FUNC,
            L"mscrlrev.dll"
            )) {
        if (ERROR_FILE_NOT_FOUND != GetLastError())
            return HError();
    }

    return S_OK;
}

//+-------------------------------------------------------------------------
//  Local functions called by CertDllVerifyRevocation
//--------------------------------------------------------------------------
PCCERT_CONTEXT GetIssuerCert(
    IN DWORD cCert,
    IN PCCERT_CONTEXT rgpCert[],
    IN DWORD dwFlags,
    IN PCERT_REVOCATION_PARA pRevPara
    );

BOOL GetSubjectCrl (
    IN PCCERT_CONTEXT pSubject,
    IN PCCERT_CONTEXT pIssuer,
    OUT PCCRL_CONTEXT* ppCrl
    );

PCRL_ENTRY FindCertInCrl(
    IN PCCERT_CONTEXT pCert,
    IN PCCRL_CONTEXT pCrl,
    IN PCERT_REVOCATION_PARA pRevPara
    );

DWORD GetCrlReason(
    IN PCRL_ENTRY pCrlEntry
    );

//+-------------------------------------------------------------------------
//  CertDllVerifyRevocation using pre-loaded CRLs in the CA store
//--------------------------------------------------------------------------
BOOL
WINAPI
CertDllVerifyRevocation(
    IN DWORD dwEncodingType,
    IN DWORD dwRevType,
    IN DWORD cContext,
    IN PVOID rgpvContext[],
    IN DWORD dwFlags,
    IN PCERT_REVOCATION_PARA pRevPara,
    IN OUT PCERT_REVOCATION_STATUS pRevStatus
    )
{
    DWORD dwError = (DWORD) CRYPT_E_NO_REVOCATION_CHECK;
    DWORD dwReason = 0;
    PCCERT_CONTEXT pCert;                       // not allocated
    PCCERT_CONTEXT pIssuer = NULL;
    PCCRL_CONTEXT pCrl = NULL;
    PCRL_ENTRY pCrlEntry;

    if (cContext == 0)
        goto NoContextError;
    if (GET_CERT_ENCODING_TYPE(dwEncodingType) != CRYPT_ASN_ENCODING)
        goto NoRevocationCheckForEncodingTypeError;
    if (dwRevType != CERT_CONTEXT_REVOCATION_TYPE)
        goto NoRevocationCheckForRevTypeError;

    pCert = (PCCERT_CONTEXT) rgpvContext[0];

    // Get the certificate's issuer
    if (NULL == (pIssuer = GetIssuerCert(
            cContext,
            (PCCERT_CONTEXT *) rgpvContext,
            dwFlags,
            pRevPara
            )))
        goto NoIssuerError;

    if (!GetSubjectCrl(
            pCert,
            pIssuer,
            &pCrl
            ))
        goto NoCrl;

    // Check if revoked
    pCrlEntry = FindCertInCrl(pCert, pCrl, pRevPara);
    if (pCrlEntry) {
        dwError = (DWORD) CRYPT_E_REVOKED;
        dwReason = GetCrlReason(pCrlEntry);
        goto Revoked;
    }

CommonReturn:
    if (pIssuer)
        CertFreeCertificateContext(pIssuer);
    if (pCrl)
        CertFreeCRLContext(pCrl);

    pRevStatus->dwIndex = 0;
    pRevStatus->dwError = dwError;
    pRevStatus->dwReason = dwReason;
    SetLastError(dwError);
    return FALSE;
ErrorReturn:
    goto CommonReturn;

TRACE_ERROR(NoContextError)
TRACE_ERROR(NoRevocationCheckForEncodingTypeError)
TRACE_ERROR(NoRevocationCheckForRevTypeError)
TRACE_ERROR(NoIssuerError)
TRACE_ERROR(NoCrl)
TRACE_ERROR(Revoked)
}


//+-------------------------------------------------------------------------
//  If the CRL entry has a CRL Reason extension, the enumerated reason
//  code is returned. Otherwise, a reason code of 0 is returned.
//--------------------------------------------------------------------------
DWORD GetCrlReason(
    IN PCRL_ENTRY pCrlEntry
    )
{
    DWORD dwReason = 0;
    PCERT_EXTENSION pExt;

    // Check if the certificate has a szOID_CRL_REASON_CODE extension
    if (pExt = CertFindExtension(
            szOID_CRL_REASON_CODE,
            pCrlEntry->cExtension,
            pCrlEntry->rgExtension
            )) {
        DWORD cbInfo = sizeof(dwReason);
        CryptDecodeObject(
            CRYPT_ASN_ENCODING,
            X509_CRL_REASON_CODE,
            pExt->Value.pbData,
            pExt->Value.cbData,
            0,                      // dwFlags
            &dwReason,
            &cbInfo);
    }
    return dwReason;
}

//+=========================================================================
//  Get Issuer Certificate Functions
//==========================================================================

PCCERT_CONTEXT FindIssuerCertInStores(
    IN PCCERT_CONTEXT pSubjectCert,
    IN DWORD cStore,
    IN HCERTSTORE rgStore[]
    )
{
    PCCERT_CONTEXT pIssuerCert = NULL;
    DWORD i;

    for (i = 0; i < cStore; i++) {
        while (TRUE) {
            DWORD dwFlags = CERT_STORE_SIGNATURE_FLAG;
            pIssuerCert = CertGetIssuerCertificateFromStore(
                rgStore[i],
                pSubjectCert,
                pIssuerCert,
                &dwFlags);
            if (NULL == pIssuerCert)
                break;
            else if (0 == (dwFlags & CERT_STORE_SIGNATURE_FLAG))
                return pIssuerCert;
        }
    }

    return NULL;
}

PCCERT_CONTEXT FindIssuerCertInDefaultStores(
    IN PCCERT_CONTEXT pSubjectCert
    )
{
    PCCERT_CONTEXT pIssuerCert;
    HCERTSTORE hStore;
    DWORD i;

    for (i = 0; i < NUM_DEFAULT_ISSUER_STORES; i++) {    
        if (hStore = CertOpenStore(
                CERT_STORE_PROV_SYSTEM_W,
                0,                          // dwEncodingType
                0,                          // hCryptProv
                rgDefaultIssuerStores[i].dwFlags | CERT_STORE_READONLY_FLAG,
                (const void *) rgDefaultIssuerStores[i].pwszStore
                )) {
            pIssuerCert = FindIssuerCertInStores(pSubjectCert, 1, &hStore);
            CertCloseStore(hStore, 0);
            if (pIssuerCert)
                return pIssuerCert;
        }
    }

    return NULL;
}

//+-------------------------------------------------------------------------
//  Get the issuer of the first certificate in the array
//--------------------------------------------------------------------------
PCCERT_CONTEXT GetIssuerCert(
    IN DWORD cCert,
    IN PCCERT_CONTEXT rgpCert[],
    IN DWORD dwFlags,
    IN PCERT_REVOCATION_PARA pRevPara
    )
{
    PCCERT_CONTEXT pSubjectCert;
    PCCERT_CONTEXT pIssuerCert = NULL;

    assert(cCert >= 1);
    pSubjectCert = rgpCert[0];
    if (cCert == 1) {
        if (pRevPara && pRevPara->cbSize >=
                (offsetof(CERT_REVOCATION_PARA, pIssuerCert) +
                    sizeof(pRevPara->pIssuerCert)))
            pIssuerCert = pRevPara->pIssuerCert;
        if (NULL == pIssuerCert && CertCompareCertificateName(
                pSubjectCert->dwCertEncodingType,
                &pSubjectCert->pCertInfo->Subject,
                &pSubjectCert->pCertInfo->Issuer))
            // Self issued
            pIssuerCert = pSubjectCert;
    } else if (dwFlags && CERT_VERIFY_REV_CHAIN_FLAG)
        pIssuerCert = rgpCert[1];

    if (pIssuerCert)
        pIssuerCert = CertDuplicateCertificateContext(pIssuerCert);
    else {
        if (pRevPara && pRevPara->cbSize >=
                (offsetof(CERT_REVOCATION_PARA, rgCertStore) +
                    sizeof(pRevPara->rgCertStore)))
            pIssuerCert = FindIssuerCertInStores(
                pSubjectCert, pRevPara->cCertStore, pRevPara->rgCertStore);
        if (NULL == pIssuerCert)
            pIssuerCert = FindIssuerCertInDefaultStores(pSubjectCert);
    }

    if (NULL == pIssuerCert)
        SetLastError(CRYPT_E_NO_REVOCATION_CHECK);
    return pIssuerCert;
}



//+-------------------------------------------------------------------------
//  Check that the CRL doesn't have any critical extensions
//--------------------------------------------------------------------------
BOOL IsExtensionValidCrl(
    IN PCCRL_CONTEXT pCrl
    )
{
    DWORD cExt = pCrl->pCrlInfo->cExtension;
    PCERT_EXTENSION pExt = pCrl->pCrlInfo->rgExtension;

    for ( ; cExt > 0; cExt--, pExt++) {
        if (pExt->fCritical)
            return FALSE;
    }

    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function:   GetSubjectCrl
//
//  Synopsis:   get the CRL associated with the subject certificate
//
//----------------------------------------------------------------------------
BOOL GetSubjectCrl (
        IN PCCERT_CONTEXT pSubject,
        IN PCCERT_CONTEXT pIssuer,
        OUT PCCRL_CONTEXT* ppCrl
        )
{
    BOOL  fResult;
    HCERTSTORE hStore;
    PCCRL_CONTEXT pFindCrl = NULL;
    DWORD dwGetCrlFlags = CERT_STORE_SIGNATURE_FLAG;

    *ppCrl = NULL;

    hStore = CertOpenSystemStoreW( NULL, L"CA" );
    if ( hStore != NULL )
    {
        while ( ( pFindCrl = CertGetCRLFromStore(
                                 hStore,
                                 pIssuer,
                                 pFindCrl,
                                 &dwGetCrlFlags
                                 ) ) != NULL )
        {
            if ( dwGetCrlFlags != 0 || !IsExtensionValidCrl( pFindCrl ))
            {
                dwGetCrlFlags = CERT_STORE_SIGNATURE_FLAG;
                continue;
            }

            *ppCrl = pFindCrl;
            break;
        }

        CertCloseStore( hStore, 0 );

        if ( *ppCrl != NULL )
        {
            return( TRUE );
        }

    }

    return( FALSE );
}

//+-------------------------------------------------------------------------
//  Find a certificate identified by its serial number in the CRL.
//--------------------------------------------------------------------------
PCRL_ENTRY FindCertInCrl(
    IN PCCERT_CONTEXT pCert,
    IN PCCRL_CONTEXT pCrl,
    IN PCERT_REVOCATION_PARA pRevPara
    )
{
    DWORD cEntry = pCrl->pCrlInfo->cCRLEntry;
    PCRL_ENTRY pEntry = pCrl->pCrlInfo->rgCRLEntry;
    DWORD cbSerialNumber = pCert->pCertInfo->SerialNumber.cbData;
    BYTE *pbSerialNumber = pCert->pCertInfo->SerialNumber.pbData;

    if (0 == cbSerialNumber)
        return NULL;

    for ( ; 0 < cEntry; cEntry--, pEntry++) {
        if (cbSerialNumber == pEntry->SerialNumber.cbData &&
                0 == memcmp(pbSerialNumber, pEntry->SerialNumber.pbData,
                                cbSerialNumber))
        {
            if (pRevPara && pRevPara->cbSize >=
                    (offsetof(CERT_REVOCATION_PARA, pftTimeToUse) +
                        sizeof(pRevPara->pftTimeToUse))
                            &&
                    NULL != pRevPara->pftTimeToUse
                            &&
                    0 > CompareFileTime(pRevPara->pftTimeToUse,
                            &pEntry->RevocationDate))
                // It was used before being revoked
                return NULL;
            else
                return pEntry;
        }
    }

    return NULL;
}