/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Microsoft Windows, Copyright (C) Microsoft Corporation, 2000 File: Chain.cpp Content: Implementation of CChain. History: 11-15-99 dsie created ------------------------------------------------------------------------------*/ // // Turn off: // // - Unreferenced formal parameter warning. // - Assignment within conditional expression warning. // #pragma warning (disable: 4100) #pragma warning (disable: 4706) #include "stdafx.h" #include "CAPICOM.h" #include "Chain.h" //////////////////////////////////////////////////////////////////////////////// // // Exported functions. // /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Function : CreateChainObject Synopsis : Create and initialize an IChain object. Parameter: PCCERT_CONTEXT pCertContext - Pointer to CERT_CONTEXT. ICertificateStatus * pIStatus - Pointer to ICertificateStatus object. VARIANT_BOOL * pVal - Pointer to VARIANT_BOOL to receive chain overall validity result. IChain ** ppIChain - Pointer to pointer to IChain object. Remark : ------------------------------------------------------------------------------*/ HRESULT CreateChainObject (PCCERT_CONTEXT pCertContext, ICertificateStatus * pIStatus, VARIANT_BOOL * pbResult, IChain ** ppIChain) { HRESULT hr = S_OK; CComObject * pCChain = NULL; DebugTrace("Entering CreateChainObject().\n"); // // Sanity check. // ATLASSERT(pCertContext); ATLASSERT(pIStatus); ATLASSERT(pbResult); ATLASSERT(ppIChain); try { // // Create the object. Note that the ref count will still be 0 // after the object is created. // if (FAILED(hr = CComObject::CreateInstance(&pCChain))) { DebugTrace("Error [%#x]: CComObject::CreateInstance() failed.\n", hr); goto ErrorExit; } // // Initialize object. // if (FAILED(hr = pCChain->Init(pCertContext, pIStatus, pbResult))) { DebugTrace("Error [%#x]: pCChain->Init() failed.\n", hr); goto ErrorExit; } // // Return IChain pointer to caller. // if (FAILED(hr = pCChain->QueryInterface(ppIChain))) { DebugTrace("Error [%#x]: pCChain->QueryInterface() failed.\n", hr); goto ErrorExit; } } catch(...) { hr = E_POINTER; DebugTrace("Exception: invalid parameter.\n"); goto ErrorExit; } CommonExit: DebugTrace("Leaving CreateChainObject().\n"); return hr; ErrorExit: // // Sanity check. // ATLASSERT(FAILED(hr)); // // Free resource. // if (pCChain) { delete pCChain; } goto CommonExit; } /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Function : GetChainContext Synopsis : Return an array of PCCERT_CONTEXT from the chain. Parameter: IChain * pIChain - Pointer to IChain. CRYPT_DATA_BLOB * pChainBlob - Pointer to blob to recevie the size and array of PCERT_CONTEXT for the chain. Remark : ------------------------------------------------------------------------------*/ STDMETHODIMP GetChainContext (IChain * pIChain, CRYPT_DATA_BLOB * pChainBlob) { HRESULT hr = S_OK; DWORD dwCerts = 0; PCCERT_CHAIN_CONTEXT pChainContext = NULL; PCERT_SIMPLE_CHAIN pSimpleChain = NULL; PCCERT_CONTEXT * rgCertContext = NULL; CComPtr pICChain = NULL; DebugTrace("Entering GetChainContext().\n"); try { // // Get ICCertificate interface pointer. // if (FAILED(hr = pIChain->QueryInterface(IID_ICChain, (void **) &pICChain))) { DebugTrace("Error [%#x]: pIChain->QueryInterface() failed.\n", hr); goto ErrorExit; } // // Get the CHAIN_CONTEXT. // if (FAILED(hr = pICChain->GetContext(&pChainContext))) { DebugTrace("Error [%#x]: pICCertificate->GetContext() failed.\n", hr); goto ErrorExit; } // // Process only the simple chain. // pSimpleChain = *pChainContext->rgpChain; // // Make sure there is cert in the chain. // if (!pSimpleChain->cElement) { // // This should not happen. There should always be at least // one cert in the chain. // hr = E_UNEXPECTED; DebugTrace("Unexpected error: no cert found in the chain.\n"); goto ErrorExit; } // // Allocate memory for array of PCERT_CONTEXT to return. // if (!(rgCertContext = (PCCERT_CONTEXT *) ::CoTaskMemAlloc(pSimpleChain->cElement * sizeof(PCCERT_CONTEXT)))) { hr = E_OUTOFMEMORY; DebugTrace("Error: out of memory.\n"); goto ErrorExit; } // // Now loop through all certs in the chain. // for (dwCerts = 0; dwCerts < pSimpleChain->cElement; dwCerts++) { // // Add the cert. // if (!(rgCertContext[dwCerts] = ::CertDuplicateCertificateContext(pSimpleChain->rgpElement[dwCerts]->pCertContext))) { hr = HRESULT_FROM_WIN32(::GetLastError()); DebugTrace("Error [%#x]: CertDuplicateCertificateContext() failed.\n", hr); goto ErrorExit; } } // // Return PCCERT_CONTEXT array. // pChainBlob->cbData = dwCerts; pChainBlob->pbData = (BYTE *) rgCertContext; } catch(...) { hr = CAPICOM_E_INTERNAL; DebugTrace("Exception: internal error.\n"); goto ErrorExit; } CommonExit: // // Free resource. // if (pChainContext) { ::CertFreeCertificateChain(pChainContext); } DebugTrace("Leaving GetChainContext().\n"); return hr; ErrorExit: // // Sanity check. // ATLASSERT(FAILED(hr)); // // Free resource. // if (rgCertContext) { while (dwCerts--) { if (rgCertContext[dwCerts]) { ::CertFreeCertificateContext(rgCertContext[dwCerts]); } } ::CoTaskMemFree((LPVOID) rgCertContext); } goto CommonExit; } /////////////////////////////////////////////////////////////////////////////// // // CChain // /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Function : CChain::get_Certificates Synopsis : Return the certificate chain in the form of ICertificates collection object. Parameter: ICertificates ** pVal - Pointer to pointer to ICertificates collection object. Remark : This collection is ordered with index 1 being the end certificate and Certificates.Count() being the root certificate. ------------------------------------------------------------------------------*/ STDMETHODIMP CChain::get_Certificates (ICertificates ** pVal) { HRESULT hr = S_OK; DebugTrace("Entering CChain::get_Certificates().\n"); try { // // Lock access to this object. // m_Lock.Lock(); // // Make sure chain has not been previously built. // if (!m_pChainContext) { hr = CAPICOM_E_CHAIN_NOT_BUILT; DebugTrace("Error: chain object does not represent a certificate chain.\n"); goto ErrorExit; } // // Return collection object to user. // if (FAILED(hr = ::CreateCertificatesObject(CAPICOM_CERTIFICATES_LOAD_FROM_CHAIN, (LPARAM) m_pChainContext, pVal))) { DebugTrace("Error [%#x]: CreateCertificatesObject() failed.\n", hr); goto ErrorExit; } } catch(...) { hr = E_POINTER; DebugTrace("Exception: invalid parameter.\n"); goto ErrorExit; } UnlockExit: // // Unlock access to this object. // m_Lock.Unlock(); return hr; ErrorExit: // // Sanity check. // ATLASSERT(FAILED(hr)); ReportError(hr); goto UnlockExit; } /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Function : CChain::get_Status Synopsis : Return validity status for the chain or a specific certificate in the chain. Parameter: long Index - 0 to specify chain status, 1 for the end cert status, or Certificates.Count() for the root cert status. long * pVal - Pointer to a long integer to receive the status, which can be ORed with the following flags: // // These can be applied to certificates and chains. // CAPICOM_TRUST_IS_NOT_TIME_VALID = 0x00000001 CAPICOM_TRUST_IS_NOT_TIME_NESTED = 0x00000002 CAPICOM_TRUST_IS_REVOKED = 0x00000004 CAPICOM_TRUST_IS_NOT_SIGNATURE_VALID = 0x00000008 CAPICOM_TRUST_IS_NOT_VALID_FOR_USAGE = 0x00000010 CAPICOM_TRUST_IS_UNTRUSTED_ROOT = 0x00000020 CAPICOM_TRUST_REVOCATION_STATUS_UNKNOWN = 0x00000040 CAPICOM_TRUST_IS_CYCLIC = 0x00000080 // // These can be applied to chains only. // CAPICOM_TRUST_IS_PARTIAL_CHAIN = 0x00010000 CAPICOM_TRUST_CTL_IS_NOT_TIME_VALID = 0x00020000 CAPICOM_TRUST_CTL_IS_NOT_SIGNATURE_VALID = 0x00040000 CAPICOM_TRUST_CTL_IS_NOT_VALID_FOR_USAGE = 0x00080000 Remark : ------------------------------------------------------------------------------*/ STDMETHODIMP CChain::get_Status (long Index, long * pVal) { HRESULT hr = S_OK; DWORD dwIndex = (DWORD) Index; DebugTrace("Entering CChain::get_Status().\n"); try { // // Lock access to this object. // m_Lock.Lock(); // // Make sure chain has been built. // if (NULL == m_pChainContext) { hr = CAPICOM_E_CHAIN_NOT_BUILT; DebugTrace("Error: chain object was not initialized.\n"); goto ErrorExit; } // // Return requested status. // if (0 == dwIndex) { *pVal = (long) m_dwStatus; } else { // // We only look at the first simple chain. // PCERT_SIMPLE_CHAIN pChain = m_pChainContext->rgpChain[0]; // // Make sure index is not out of range. // if (dwIndex-- > pChain->cElement) { hr = E_INVALIDARG; DebugTrace("Error: invalid parameter, certificate index out of range.\n"); goto ErrorExit; } *pVal = (long) pChain->rgpElement[dwIndex]->TrustStatus.dwErrorStatus; } } catch(...) { hr = E_POINTER; DebugTrace("Exception: invalid parameter.\n"); goto ErrorExit; } UnlockExit: // // Unlock access to this object. // m_Lock.Unlock(); return hr; ErrorExit: // // Sanity check. // ATLASSERT(FAILED(hr)); ReportError(hr); goto UnlockExit; } /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Function : CChain::Build Synopsis : Build the chain. Parameter: ICertificate * pICertificate - Pointer to certificate for which the chain is to build. VARIANT_BOOL * pVal - Pointer to VARIANT_BOOL to receive chain overall validity result. Remark : ------------------------------------------------------------------------------*/ STDMETHODIMP CChain::Build (ICertificate * pICertificate, VARIANT_BOOL * pVal) { HRESULT hr = S_OK; PCCERT_CONTEXT pCertContext = NULL; CComPtr pIStatus = NULL; VARIANT_BOOL bResult; DebugTrace("Entering CChain::Build().\n"); try { // // Lock access to this object. // m_Lock.Lock(); // // Get CERT_CONTEXT. // if (FAILED(hr = ::GetCertContext(pICertificate, &pCertContext))) { DebugTrace("Error [%#x]: GetCertContext() failed.\n", hr); goto ErrorExit; } // // Get status check object. // if (FAILED(hr = pICertificate->IsValid(&pIStatus))) { DebugTrace("Error [%#x]: pICertificate->IsValid() failed.\n", hr); goto ErrorExit; } // // Build the chain. // if (FAILED(hr = Init(pCertContext, pIStatus, &bResult))) { DebugTrace("Error [%#x]: CChain::Init() failed.\n", hr); goto ErrorExit; } // // Return result to caller. // *pVal = bResult; } catch(...) { hr = E_POINTER; DebugTrace("Exception: invalid parameter.\n"); goto ErrorExit; } UnlockExit: // // Free resource. // if (pCertContext) { ::CertFreeCertificateContext(pCertContext); } // // Unlock access to this object. // m_Lock.Unlock(); return hr; ErrorExit: // // Sanity check. // ATLASSERT(FAILED(hr)); ReportError(hr); goto UnlockExit; } //////////////////////////////////////////////////////////////////////////////// // // Custom interfaces. // /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Function : CChain::GetContext Synopsis : Return an array of PCCERT_CONTEXT from the chain. Parameter: CRYPT_DATA_BLOB * pVal - Pointer to blob to recevie the size and array of PCERT_CONTEXT for the chain. Remark : This restricted method is designed for internal use only, and therefore, should not be exposed to user. ------------------------------------------------------------------------------*/ STDMETHODIMP CChain::GetContext (PCCERT_CHAIN_CONTEXT * ppChainContext) { HRESULT hr = S_OK; PCCERT_CHAIN_CONTEXT pChainContext = NULL; DebugTrace("Entering CChain::GetContext().\n"); try { // // Lock access to this object. // m_Lock.Lock(); // // Make sure chain has been built. // if (!m_pChainContext) { hr = CAPICOM_E_CHAIN_NOT_BUILT; DebugTrace("Error: chain object was not initialized.\n"); goto ErrorExit; } // // Duplicate the chain context. // if (!(pChainContext = ::CertDuplicateCertificateChain(m_pChainContext))) { hr = HRESULT_FROM_WIN32(::GetLastError()); DebugTrace("Error [%#x]: CertDuplicateCertificateChain() failed.\n"); goto ErrorExit; } // // and return to caller. // *ppChainContext = pChainContext; } catch(...) { hr = E_POINTER; DebugTrace("Exception: invalid parameter.\n"); goto ErrorExit; } UnlockExit: // // Unlock access to this object. // m_Lock.Unlock(); DebugTrace("Leaving CChain::GetContext().\n"); return hr; ErrorExit: // // Sanity check. // ATLASSERT(FAILED(hr)); // // Free resource. // if (pChainContext) { ::CertFreeCertificateChain(pChainContext); } goto UnlockExit; } //////////////////////////////////////////////////////////////////////////////// // // Private methods. // /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Function : CChain::Init Synopsis : Initialize the object. Parameter: PCCERT_CONTEXT pCertContext - Pointer to CERT_CONTEXT. ICertificateStatus * pIStatus - Pointer to ICertificateStus object used to build the chain. VARIANT_BOOL * pVal - Pointer to VARIANT_BOOL to receive chain overall validity result. Remark : This method is not part of the COM interface (it is a normal C++ member function). We need it to initialize the object created internally by us with CERT_CONTEXT. Since it is only a normal C++ member function, this function can only be called from a C++ class pointer, not an interface pointer. ------------------------------------------------------------------------------*/ STDMETHODIMP CChain::Init (PCCERT_CONTEXT pCertContext, ICertificateStatus * pIStatus, VARIANT_BOOL * pbResult) { HRESULT hr = S_OK; CAPICOM_CHECK_FLAG UserFlags = CAPICOM_CHECK_NONE; DWORD dwCheckFlags = 0; CComPtr pIEku = NULL; CComBSTR bstrEkuOid = NULL; LPSTR lpszEkuOid = NULL; PCCERT_CHAIN_CONTEXT pChainContext = NULL; CERT_CHAIN_PARA ChainPara = {sizeof(CERT_CHAIN_PARA), {USAGE_MATCH_TYPE_AND, {0, NULL}}}; // // For OLE2A macro. // USES_CONVERSION; DebugTrace("Entering CChain::Init().\n"); // // Sanity check. // ATLASSERT(pCertContext); ATLASSERT(pIStatus); ATLASSERT(pbResult); // // Get the user's requested check flag. // if (FAILED(hr = pIStatus->get_CheckFlag(&UserFlags))) { DebugTrace("Error [%#x]: pIStatus->CheckFlag() failed.\n", hr); goto ErrorExit; } // // Set check flags. // if (CAPICOM_CHECK_ONLINE_REVOCATION_STATUS & UserFlags) { dwCheckFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN; } if (CAPICOM_CHECK_OFFLINE_REVOCATION_STATUS & UserFlags) { dwCheckFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN | CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY; } // // Get EKU object. // if (FAILED(hr = pIStatus->EKU(&pIEku))) { DebugTrace("Error [%#x]: pIStatus->EKU() failed.\n", hr); goto ErrorExit; } // // Get EKU OID value. // if (FAILED(hr = pIEku->get_OID(&bstrEkuOid))) { if (CAPICOM_E_EKU_OID_NOT_INITIALIZED == hr) { // // Don't care if no OID is set. // hr = S_OK; } else { DebugTrace("Error [%#x]: pIEku->get_OID() failed.\n", hr); goto ErrorExit; } } // // If not empty, then set EKU. // if (bstrEkuOid && bstrEkuOid.Length()) { // // Use OLE2A which will allocate LPSTR off the stack, and // avoid having to free it explicitly, as it will be freed // automatically when the function exits. // if (!(lpszEkuOid = OLE2A(bstrEkuOid))) { hr = E_OUTOFMEMORY; DebugTrace("Error: out of memory.\n"); goto ErrorExit; } ChainPara.RequestedUsage.Usage.cUsageIdentifier = 1; ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = &lpszEkuOid; } // // Build the chain. // if (!::CertGetCertificateChain(NULL, // in optional pCertContext, // in NULL, // in optional NULL, // in optional &ChainPara, // in dwCheckFlags, // in NULL, // in &pChainContext)) // out { hr = HRESULT_FROM_WIN32(::GetLastError()); DebugTrace("Error [%#x]: CertGetCertificateChain() failed.\n", hr); goto ErrorExit; } // // Update member variables. // if (m_pChainContext) { ::CertFreeCertificateChain(m_pChainContext); } m_pChainContext = pChainContext; m_dwStatus = pChainContext->TrustStatus.dwErrorStatus; // // Check results. // if (((ChainPara.RequestedUsage.Usage.cUsageIdentifier) && ((CERT_TRUST_IS_NOT_VALID_FOR_USAGE | CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE) & m_dwStatus)) || ((UserFlags & CAPICOM_CHECK_SIGNATURE_VALIDITY) && ((CERT_TRUST_IS_NOT_SIGNATURE_VALID | CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID) & m_dwStatus)) || ((UserFlags & CAPICOM_CHECK_TIME_VALIDITY) && ((CERT_TRUST_IS_NOT_TIME_VALID | CERT_TRUST_CTL_IS_NOT_TIME_VALID) & m_dwStatus)) || ((UserFlags & CAPICOM_CHECK_TRUSTED_ROOT) && (CERT_TRUST_IS_UNTRUSTED_ROOT & m_dwStatus)) || ((UserFlags & CAPICOM_CHECK_ONLINE_REVOCATION_STATUS || UserFlags & CAPICOM_CHECK_OFFLINE_REVOCATION_STATUS) && (CERT_TRUST_IS_REVOKED & m_dwStatus)) || (CERT_TRUST_IS_PARTIAL_CHAIN & m_dwStatus)) { *pbResult = VARIANT_FALSE; DebugTrace("Info: invalid chain (status = %#x).\n", m_dwStatus); goto CommonExit; } // // If requested to check revocation status, then we need to walk the // chain for CERT_TRUST_REVOCATION_STATUS_UNKNOWN to see if there is // really any revocation error. // if ((UserFlags & CAPICOM_CHECK_ONLINE_REVOCATION_STATUS || UserFlags & CAPICOM_CHECK_OFFLINE_REVOCATION_STATUS) && (CERT_TRUST_REVOCATION_STATUS_UNKNOWN & pChainContext->TrustStatus.dwErrorStatus)) { // // Walk thru every chain. // for (DWORD i = 0; i < pChainContext->cChain; i++) { PCERT_SIMPLE_CHAIN pChain = pChainContext->rgpChain[i]; // // Walk thru every element of each chain. // for (DWORD j = 0; j < pChain->cElement; j++) { PCERT_CHAIN_ELEMENT pElement = pChain->rgpElement[j]; // // Is this element with unknown status? // if (CERT_TRUST_REVOCATION_STATUS_UNKNOWN & pElement->TrustStatus.dwErrorStatus) { // // Consider an error (CRL server is offline) if the revocation // status is not CRYPT_E_NO_REVOCATION_CHECK. // if (CRYPT_E_NO_REVOCATION_CHECK != pElement->pRevocationInfo->dwRevocationResult) { *pbResult = VARIANT_FALSE; DebugTrace("Info: invalid chain (CRYPT_E_REVOCATION_OFFLINE).\n"); goto CommonExit; } } } } #if (0) // // If we get to here, then we need to throw away // CERT_TRUST_REVOCATION_STATUS_UNKNOWN in the status, and thus // treat it as no error. // m_dwStatus &= ~CERT_TRUST_REVOCATION_STATUS_UNKNOWN; #endif } // // Everything checks out OK. // *pbResult = VARIANT_TRUE; CommonExit: DebugTrace("Leaving CChain::Init().\n"); return hr; ErrorExit: // // Sanity check. // ATLASSERT(FAILED(hr)); // // Free resouce. // if (pChainContext) { ::CertFreeCertificateChain(pChainContext); } goto CommonExit; }