//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1996 - 1999 // // File: certtrst.cpp // // Contents: Microsoft Internet Security Provider // // Functions: WintrustCertificateTrust // // *** local functions *** // _WalkChain // _IsLifetimeSigningCert // // History: 07-Jun-1997 pberkman created // //-------------------------------------------------------------------------- #include "global.hxx" // // support for MS test roots!!!! // static BYTE rgbTestRoot[] = { # include "certs\\mstest1.h" }; static BYTE rgbTestRootCorrected[] = { # include "certs\\mstest2.h" }; static BYTE rgbTestRootBeta1[] = { # include "certs\\mstestb1.h" }; #define NTESTROOTS 3 static CERT_PUBLIC_KEY_INFO rgTestRootPublicKeyInfo[NTESTROOTS] = { {szOID_RSA_RSA, 0, NULL, sizeof(rgbTestRoot), rgbTestRoot, 0}, {szOID_RSA_RSA, 0, NULL, sizeof(rgbTestRootCorrected), rgbTestRootCorrected, 0}, {szOID_RSA_RSA, 0, NULL, sizeof(rgbTestRootBeta1), rgbTestRootBeta1, 0} }; BOOL _WalkChain( CRYPT_PROVIDER_DATA *pProvData, DWORD idxSigner, DWORD *pdwError, BOOL fCounterSigner, DWORD idxCounterSigner, BOOL fTimeStamped, BOOL *pfLifetimeSigning // IN OUT, only accessed for fTimeStamped ); BOOL WINAPI _IsLifetimeSigningCert( PCCERT_CONTEXT pCertContext ); HRESULT WINAPI WintrustCertificateTrust(CRYPT_PROVIDER_DATA *pProvData) { if ((_ISINSTRUCT(CRYPT_PROVIDER_DATA, pProvData->cbStruct, fRecallWithState)) && (pProvData->fRecallWithState == TRUE)) { return(S_OK); } DWORD dwError; dwError = S_OK; if (pProvData->csSigners < 1) { pProvData->padwTrustStepErrors[TRUSTERROR_STEP_FINAL_CERTPROV] = TRUST_E_NOSIGNATURE; return(S_FALSE); } // // loop through all signers // for (int i = 0; i < (int)pProvData->csSigners; i++) { BOOL fTimeStamped = FALSE; BOOL fLifetimeSigning = FALSE; if (pProvData->pasSigners[i].csCertChain < 1) { pProvData->pasSigners[i].dwError = TRUST_E_NO_SIGNER_CERT; pProvData->padwTrustStepErrors[TRUSTERROR_STEP_FINAL_CERTPROV] = TRUST_E_NO_SIGNER_CERT; continue; } // Check if timestamped. if (0 < pProvData->pasSigners[i].csCounterSigners && (pProvData->pasSigners[i].pasCounterSigners[0].dwSignerType & SGNR_TYPE_TIMESTAMP)) { fTimeStamped = TRUE; // See if LifeTime Signing has been enabled if (pProvData->dwProvFlags & WTD_LIFETIME_SIGNING_FLAG) { fLifetimeSigning = TRUE; } else { // Check if the signer certificate has the LIFETIME_SIGNING // EKU. fLifetimeSigning = _IsLifetimeSigningCert( pProvData->pasSigners[i].pasCertChain[0].pCert); } } _WalkChain(pProvData, i, &dwError, FALSE, 0, fTimeStamped, &fLifetimeSigning); for (int i2 = 0; i2 < (int)pProvData->pasSigners[i].csCounterSigners; i2++) { if (pProvData->pasSigners[i].pasCounterSigners[i2].csCertChain < 1) { pProvData->pasSigners[i].pasCounterSigners[i2].dwError = TRUST_E_NO_SIGNER_CERT; pProvData->padwTrustStepErrors[TRUSTERROR_STEP_FINAL_CERTPROV] = TRUST_E_COUNTER_SIGNER; dwError = S_FALSE; continue; } // If lifetime signing has been enabled, use current time instead // of timestamp time. if (fLifetimeSigning) { memcpy(&pProvData->pasSigners[i].pasCounterSigners[i2].sftVerifyAsOf, &pProvData->sftSystemTime, sizeof(FILETIME)); } _WalkChain(pProvData, i, &dwError, TRUE, i2, FALSE, NULL); } } return(dwError); } HCERTCHAINENGINE GetChainEngine( IN CRYPT_PROVIDER_DATA *pProvData ) { CERT_CHAIN_ENGINE_CONFIG Config; HCERTSTORE hStore = NULL; HCERTCHAINENGINE hChainEngine = NULL; if (NULL == pProvData->pWintrustData || pProvData->pWintrustData->dwUnionChoice != WTD_CHOICE_CERT || !_ISINSTRUCT(WINTRUST_CERT_INFO, pProvData->pWintrustData->pCert->cbStruct, dwFlags) || 0 == (pProvData->pWintrustData->pCert->dwFlags & (WTCI_DONT_OPEN_STORES | WTCI_OPEN_ONLY_ROOT))) return NULL; memset(&Config, 0, sizeof(Config)); Config.cbSize = sizeof(Config); if (NULL == (hStore = CertOpenStore( CERT_STORE_PROV_MEMORY, 0, // dwEncodingType 0, // hCryptProv 0, // dwFlags NULL // pvPara ))) goto OpenMemoryStoreError; if (pProvData->pWintrustData->pCert->dwFlags & WTCI_DONT_OPEN_STORES) Config.hRestrictedRoot = hStore; Config.hRestrictedTrust = hStore; Config.hRestrictedOther = hStore; if (!CertCreateCertificateChainEngine( &Config, &hChainEngine )) goto CreateChainEngineError; CommonReturn: CertCloseStore(hStore, 0); return hChainEngine; ErrorReturn: hChainEngine = NULL; goto CommonReturn; TRACE_ERROR_EX(DBG_SS, OpenMemoryStoreError) TRACE_ERROR_EX(DBG_SS, CreateChainEngineError) } HCERTSTORE GetChainAdditionalStore( IN CRYPT_PROVIDER_DATA *pProvData ) { HCERTSTORE hStore = NULL; if (0 == pProvData->chStores) return NULL; if (1 < pProvData->chStores) { if (hStore = CertOpenStore( CERT_STORE_PROV_COLLECTION, 0, // dwEncodingType 0, // hCryptProv 0, // dwFlags NULL // pvPara )) { DWORD i; for (i = 0; i < pProvData->chStores; i++) CertAddStoreToCollection( hStore, pProvData->pahStores[i], CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG, 0 // dwPriority ); } } else hStore = CertDuplicateStore(pProvData->pahStores[0]); #if 0 CertSaveStore( hStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, CERT_STORE_SAVE_AS_STORE, CERT_STORE_SAVE_TO_FILENAME_A, (void *) "C:\\temp\\wintrust.sto", 0 // dwFlags ); #endif return hStore; } // Following is in ..\softpub\callui.cpp extern HRESULT _CheckTrustedCodeHash(CRYPT_PROVIDER_DATA *pProvData); BOOL UpdateCertProvChain( IN OUT PCRYPT_PROVIDER_DATA pProvData, IN DWORD idxSigner, OUT DWORD *pdwError, IN BOOL fCounterSigner, IN DWORD idxCounterSigner, IN PCRYPT_PROVIDER_SGNR pSgnr, IN PCCERT_CHAIN_CONTEXT pChainContext ) { BOOL fTestCert = FALSE; DWORD dwSgnrError = 0; DWORD i; // The chain better have at least the certificate we passed in assert(0 < pChainContext->cChain && 0 < pChainContext->rgpChain[0]->cElement); for (i = 0; i < pChainContext->cChain; i++) { DWORD j; PCERT_SIMPLE_CHAIN pChain = pChainContext->rgpChain[i]; for (j = 0; j < pChain->cElement; j++) { PCERT_CHAIN_ELEMENT pEle = pChain->rgpElement[j]; DWORD dwEleError = pEle->TrustStatus.dwErrorStatus; DWORD dwEleInfo = pEle->TrustStatus.dwInfoStatus; PCRYPT_PROVIDER_CERT pProvCert; if (0 != i || 0 != j) { if (!(pProvData->psPfns->pfnAddCert2Chain( pProvData, idxSigner, fCounterSigner, idxCounterSigner, pEle->pCertContext))) { pProvData->dwError = GetLastError(); dwSgnrError = TRUST_E_SYSTEM_ERROR; goto CommonReturn; } } // // else // Signer cert has already been added pProvCert = &pSgnr->pasCertChain[pSgnr->csCertChain -1]; //DSIE: 12-Oct-2000 added to get pChainElement. pProvCert->pChainElement = pEle; pProvCert->fSelfSigned = 0 != (dwEleInfo & CERT_TRUST_IS_SELF_SIGNED) && 0 == (dwEleError & CERT_TRUST_IS_NOT_SIGNATURE_VALID); pProvCert->fTrustedRoot = pProvCert->fSelfSigned && i == pChainContext->cChain - 1 && j == pChain->cElement - 1 && 0 == (dwEleError & CERT_TRUST_IS_UNTRUSTED_ROOT); if (pProvCert->fSelfSigned) { // Check if one of the "test" roots DWORD k; for (k = 0; k < NTESTROOTS; k++) { if (CertComparePublicKeyInfo( pProvData->dwEncoding, &pProvCert->pCert->pCertInfo->SubjectPublicKeyInfo, &rgTestRootPublicKeyInfo[k])) { pProvCert->fTestCert = TRUE; fTestCert = TRUE; if (pProvData->dwRegPolicySettings & WTPF_TRUSTTEST) pProvCert->fTrustedRoot = TRUE; break; } } } // First Element in all but the first simple chain pProvCert->fTrustListSignerCert = (0 < i && 0 == j); pProvCert->fIsCyclic = (0 != (dwEleError & CERT_TRUST_IS_CYCLIC)); // Map to IE4Trust confidence if (0 == (dwEleError & CERT_TRUST_IS_NOT_SIGNATURE_VALID)) pProvCert->dwConfidence |= CERT_CONFIDENCE_SIG; if (0 == (dwEleError & CERT_TRUST_IS_NOT_TIME_VALID)) pProvCert->dwConfidence |= CERT_CONFIDENCE_TIME; // On Sep 10, 1998 Trevor/Brian wanted time nesting checks to // be disabled // if (0 == (dwEleError & CERT_TRUST_IS_NOT_TIME_NESTED)) pProvCert->dwConfidence |= CERT_CONFIDENCE_TIMENEST; if (0 != (dwEleInfo & CERT_TRUST_HAS_EXACT_MATCH_ISSUER)) pProvCert->dwConfidence |= CERT_CONFIDENCE_AUTHIDEXT; if (0 == (dwEleError & CERT_TRUST_IS_NOT_SIGNATURE_VALID) && 0 != (dwEleInfo & CERT_TRUST_HAS_EXACT_MATCH_ISSUER)) pProvCert->dwConfidence |= CERT_CONFIDENCE_HYGIENE; if (pEle->pRevocationInfo) { pProvCert->dwRevokedReason = pEle->pRevocationInfo->dwRevocationResult; } // Update any signature or revocations errors if (dwEleError & CERT_TRUST_IS_NOT_SIGNATURE_VALID) { pProvCert->dwError = TRUST_E_CERT_SIGNATURE; assert(pChainContext->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID); dwSgnrError = TRUST_E_CERT_SIGNATURE; } else if (dwEleError & CERT_TRUST_IS_REVOKED) { pProvCert->dwError = CERT_E_REVOKED; assert(pChainContext->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED); if (0 == dwSgnrError || CERT_E_REVOCATION_FAILURE == dwSgnrError) dwSgnrError = CERT_E_REVOKED; #if 0 // On 8-April-2002 removed the following. Revocation checking // is no longer done for partial chains. // // Also, this was only set for WTD_REVOKE_WHOLECHAIN. } else if (dwEleError & CERT_TRUST_IS_OFFLINE_REVOCATION) { // Ignore NO_CHECK errors if (pProvData->pWintrustData->fdwRevocationChecks == WTD_REVOKE_WHOLECHAIN) { pProvCert->dwError = CERT_E_REVOCATION_FAILURE; assert(pChainContext->TrustStatus.dwErrorStatus & CERT_TRUST_REVOCATION_STATUS_UNKNOWN); if (0 == dwSgnrError) dwSgnrError = CERT_E_REVOCATION_FAILURE; } #endif } // If last element in simple chain, check if it was in a // CTL and update CryptProvData if it was. if (j == pChain->cElement - 1 && pChain->pTrustListInfo && pChain->pTrustListInfo->pCtlContext) { DWORD dwChainError = pChain->TrustStatus.dwErrorStatus; // Note, don't need to AddRef since we already hold an // AddRef on the ChainContext. pProvCert->pCtlContext = pChain->pTrustListInfo->pCtlContext; if (dwChainError & CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID) { pProvCert->dwCtlError = TRUST_E_CERT_SIGNATURE; dwSgnrError = TRUST_E_CERT_SIGNATURE; } else if (dwChainError & CERT_TRUST_CTL_IS_NOT_TIME_VALID) { if (0 == (pProvData->dwRegPolicySettings & WTPF_IGNOREEXPIRATION)) pProvCert->dwCtlError = CERT_E_EXPIRED; } else if (dwChainError & CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE) { pProvCert->dwCtlError = CERT_E_WRONG_USAGE; } } if (pProvData->psPfns->pfnCertCheckPolicy) { if (! (*pProvData->psPfns->pfnCertCheckPolicy)( pProvData, idxSigner, fCounterSigner, idxCounterSigner)) goto CommonReturn; } } } CommonReturn: if (fTestCert) { if (CERT_TRUST_IS_REVOKED == dwSgnrError || CERT_E_REVOCATION_FAILURE == dwSgnrError) { // No revocation errors for "test" roots dwSgnrError = 0; // Loop through certs and remove any revocation error status for (i = 0; i < pSgnr->csCertChain; i++) { PCRYPT_PROVIDER_CERT pProvCert = &pSgnr->pasCertChain[i]; pProvCert->dwError = 0; pProvCert->dwRevokedReason = 0; } } } if (CERT_E_REVOCATION_FAILURE == dwSgnrError && pProvData->pWintrustData->fdwRevocationChecks != WTD_REVOKE_WHOLECHAIN) // Will check during Final Policy dwSgnrError = 0; if (CERT_E_REVOKED == dwSgnrError) { if (S_OK == _CheckTrustedCodeHash(pProvData)) { // The code was explicitly trusted. dwSgnrError = 0; } } if (dwSgnrError) { pSgnr->dwError = dwSgnrError; pProvData->padwTrustStepErrors[TRUSTERROR_STEP_FINAL_CERTPROV] = dwSgnrError; *pdwError = S_FALSE; return FALSE; } else return TRUE; } BOOL _WalkChain( CRYPT_PROVIDER_DATA *pProvData, DWORD idxSigner, DWORD *pdwError, BOOL fCounterSigner, DWORD idxCounterSigner, BOOL fTimeStamped, BOOL *pfLifetimeSigning // IN OUT, only accessed for fTimeStamped ) { BOOL fResult; DWORD dwCreateChainFlags; DWORD dwSgnrError = 0; CRYPT_PROVIDER_SGNR *pSgnr; // not allocated PCCERT_CONTEXT pCertContext; // not allocated CERT_CHAIN_PARA ChainPara; HCERTCHAINENGINE hChainEngine = NULL; HCERTSTORE hAdditionalStore = NULL; PCCERT_CHAIN_CONTEXT pChainContext = NULL; LPSTR pszUsage = NULL; if (fCounterSigner) pSgnr = &pProvData->pasSigners[idxSigner].pasCounterSigners[ idxCounterSigner]; else pSgnr = &pProvData->pasSigners[idxSigner]; assert(pSgnr); // // at this stage, the last cert in the chain "should be" the signers cert. // eg: there should only be one cert in the chain from the Signature // Provider // if (1 != pSgnr->csCertChain || (NULL == (pCertContext = pSgnr->pasCertChain[pSgnr->csCertChain - 1].pCert))) { dwSgnrError = TRUST_E_NO_SIGNER_CERT; goto NoSignerCertError; } memset(&ChainPara, 0, sizeof(ChainPara)); ChainPara.cbSize = sizeof(ChainPara); if (fCounterSigner && SGNR_TYPE_TIMESTAMP == pSgnr->dwSignerType) { pszUsage = szOID_PKIX_KP_TIMESTAMP_SIGNING; } else if(NULL != pProvData->pRequestUsage) { ChainPara.RequestedUsage = *pProvData->pRequestUsage; } else { pszUsage = pProvData->pszUsageOID; } if ( (0 == (pProvData->dwProvFlags & WTD_NO_POLICY_USAGE_FLAG)) && pszUsage) { ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; ChainPara.RequestedUsage.Usage.cUsageIdentifier = 1; ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = &pszUsage; } hChainEngine = GetChainEngine(pProvData); hAdditionalStore = GetChainAdditionalStore(pProvData); dwCreateChainFlags = 0; if (pProvData->dwProvFlags & CPD_REVOCATION_CHECK_NONE) { ; } else if (pProvData->dwProvFlags & CPD_REVOCATION_CHECK_END_CERT) { dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_END_CERT; } else if (pProvData->dwProvFlags & CPD_REVOCATION_CHECK_CHAIN) { dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN; } else if (pProvData->dwProvFlags & CPD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT) { dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; } else if (pProvData->dwProvFlags & WTD_REVOCATION_CHECK_NONE) { ; } else if (pProvData->dwProvFlags & WTD_REVOCATION_CHECK_END_CERT) { dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_END_CERT; } else if (pProvData->dwProvFlags & WTD_REVOCATION_CHECK_CHAIN) { dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN; } else if (pProvData->dwProvFlags & WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT) { dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; } else if (pProvData->pWintrustData->fdwRevocationChecks == WTD_REVOKE_WHOLECHAIN) { dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN; } else if (fCounterSigner && SGNR_TYPE_TIMESTAMP == pSgnr->dwSignerType) { if (0 == (pProvData->dwRegPolicySettings & WTPF_IGNOREREVOCATIONONTS)) // On 4-12-01 changed from END_CERT to EXCLUDE_ROOT dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; } else if (0 == (pProvData->dwRegPolicySettings & WTPF_IGNOREREVOKATION)) // On 4-12-01 changed from END_CERT to EXCLUDE_ROOT dwCreateChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; if (!(pProvData->pWintrustData->dwUnionChoice == WTD_CHOICE_CERT || pProvData->pWintrustData->dwUnionChoice == WTD_CHOICE_SIGNER)) // Certificate was obtained from either the message store, or one // of our known stores dwCreateChainFlags |= CERT_CHAIN_CACHE_END_CERT; // else // Can't implicitly cache a context passed to us if (pProvData->dwProvFlags & WTD_CACHE_ONLY_URL_RETRIEVAL) { dwCreateChainFlags |= CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL | CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY; } #if 0 { HCERTSTORE hDebugStore; if (hDebugStore = CertOpenSystemStoreA(0, "wintrust")) { CertAddCertificateContextToStore( hDebugStore, pCertContext, CERT_STORE_ADD_ALWAYS, NULL ); CertCloseStore(hDebugStore, 0); } } #endif FILETIME fileTime; memset(&fileTime, 0, sizeof(fileTime)); if (fTimeStamped && *pfLifetimeSigning) dwCreateChainFlags |= CERT_CHAIN_TIMESTAMP_TIME; if (!CertGetCertificateChain ( hChainEngine, pCertContext, (memcmp(&pSgnr->sftVerifyAsOf, &fileTime, sizeof(fileTime)) == 0) ? NULL : &pSgnr->sftVerifyAsOf, hAdditionalStore, &ChainPara, dwCreateChainFlags, NULL, // pvReserved, &pChainContext )) { pProvData->dwError = GetLastError(); dwSgnrError = TRUST_E_SYSTEM_ERROR; goto GetChainError; } if (fTimeStamped && !*pfLifetimeSigning) { // See if resultant application policy has the LIFETIME_SIGNING OID PCERT_ENHKEY_USAGE pAppUsage = pChainContext->rgpChain[0]->rgpElement[0]->pApplicationUsage; if (pAppUsage) { DWORD i; for (i = 0; i < pAppUsage->cUsageIdentifier; i++) { if (0 == strcmp(pAppUsage->rgpszUsageIdentifier[i], szOID_KP_LIFETIME_SIGNING)) { *pfLifetimeSigning = TRUE; break; } } } if (*pfLifetimeSigning) { CertFreeCertificateChain(pChainContext); pChainContext = NULL; dwCreateChainFlags |= CERT_CHAIN_TIMESTAMP_TIME; if (!CertGetCertificateChain ( hChainEngine, pCertContext, (memcmp(&pSgnr->sftVerifyAsOf, &fileTime, sizeof(fileTime)) == 0) ? NULL : &pSgnr->sftVerifyAsOf, hAdditionalStore, &ChainPara, dwCreateChainFlags, NULL, // pvReserved, &pChainContext )) { pProvData->dwError = GetLastError(); dwSgnrError = TRUST_E_SYSTEM_ERROR; goto GetChainError; } } } pSgnr->pChainContext = pChainContext; if (pProvData->dwProvFlags & WTD_NO_IE4_CHAIN_FLAG) { pProvData->dwProvFlags |= CPD_USE_NT5_CHAIN_FLAG; fResult = TRUE; } else fResult = UpdateCertProvChain( pProvData, idxSigner, pdwError, fCounterSigner, idxCounterSigner, pSgnr, pChainContext ); CommonReturn: if (hChainEngine) CertFreeCertificateChainEngine(hChainEngine); if (hAdditionalStore) CertCloseStore(hAdditionalStore, 0); return fResult; ErrorReturn: pSgnr->dwError = dwSgnrError; pProvData->padwTrustStepErrors[TRUSTERROR_STEP_FINAL_CERTPROV] = dwSgnrError; *pdwError = S_FALSE; fResult = FALSE; goto CommonReturn; TRACE_ERROR_EX(DBG_SS, NoSignerCertError) TRACE_ERROR_EX(DBG_SS, GetChainError) } BOOL WINAPI _IsLifetimeSigningCert( PCCERT_CONTEXT pCertContext ) { DWORD cbSize; PCERT_ENHKEY_USAGE pCertEKU; // // see if the certificate has the proper enhanced key usage OID // cbSize = 0; CertGetEnhancedKeyUsage(pCertContext, CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG, NULL, &cbSize); if (cbSize == 0) { return(FALSE); } if (!(pCertEKU = (PCERT_ENHKEY_USAGE)new BYTE[cbSize])) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return(FALSE); } if (!(CertGetEnhancedKeyUsage(pCertContext, CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG, pCertEKU, &cbSize))) { delete pCertEKU; return(FALSE); } for (int i = 0; i < (int)pCertEKU->cUsageIdentifier; i++) { if (strcmp(pCertEKU->rgpszUsageIdentifier[i], szOID_KP_LIFETIME_SIGNING) == 0) { delete pCertEKU; return(TRUE); } } delete pCertEKU; return(FALSE); }