2025-04-27 07:49:33 -04:00

938 lines
25 KiB
C++

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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<CChain> * 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<CChain>::CreateInstance(&pCChain)))
{
DebugTrace("Error [%#x]: CComObject<CChain>::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<ICChain> 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<ICertificateStatus> 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<IEKU> 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;
}