//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1996 - 1999
//
//  File:       expapis.cpp
//
//  Contents:   Microsoft Internet Security Trust Provider
//
//  Functions:  FindCertsByIssuer
//
//  History:    01-Jun-1997 pberkman   created
//
//--------------------------------------------------------------------------

#include    "global.hxx"
/////////////////////////////////////////////////////////////////////////////

//
// The root of the certificate store that we manage.
//
#define HEAPALLOC(size)  HeapAlloc ( GetProcessHeap(), 0, size )
#define HEAPFREE(data)   HeapFree  ( GetProcessHeap(), 0, data )


#define SZIE30CERTCLIENTAUTH "Software\\Microsoft\\Cryptography\\PersonalCertificates\\ClientAuth"
#define SZIE30TAGS       "CertificateTags"
#define SZIE30AUXINFO        "CertificateAuxiliaryInfo"
#define SZIE30CERTBUCKET     "Certificates"

#define ALIGN_LEN(Len)  ((Len + 7) & ~7)

#define IE30CONVERTEDSTORE  "My"

static LPCSTR rgpszMyStore[] = {
    "My"
};
#define NMYSTORES (sizeof(rgpszMyStore)/sizeof(rgpszMyStore[0]))

static const struct {
    LPCSTR      pszStore;
    DWORD       dwFlags;
} rgCaStoreInfo[] = {
    "ROOT",     CERT_SYSTEM_STORE_CURRENT_USER,
    "CA",       CERT_SYSTEM_STORE_CURRENT_USER,
    "SPC",      CERT_SYSTEM_STORE_LOCAL_MACHINE
};
#define NCASTORES (sizeof(rgCaStoreInfo)/sizeof(rgCaStoreInfo[0]))

#define MAX_CHAIN_LEN   16
typedef struct _CHAIN_INFO CHAIN_INFO, *PCHAIN_INFO;
struct _CHAIN_INFO {
    DWORD           cCert;
    PCCERT_CONTEXT  rgpCert[MAX_CHAIN_LEN];
    DWORD           cbKeyProvInfo;          // aligned
    DWORD           cbCert;                 // aligned
    PCHAIN_INFO     pNext;
};

//+-------------------------------------------------------------------------
//  AuthCert allocation and free functions
//--------------------------------------------------------------------------
static void *ACAlloc(
    IN size_t cbBytes
    )
{
    void *pv;
    pv = (void *)new BYTE[cbBytes];
    if (pv == NULL)
       SetLastError(ERROR_NOT_ENOUGH_MEMORY);
    return pv;
}
static void ACFree(
    IN void *pv
    )
{
    if (pv)
    {
        delete pv;
    }
}

static HRESULT GetAndIe30ClientAuthCertificates(HCERTSTORE hStore)
// Check for and copy any existing certificates stored in Bob's
// certificate store.
{
    HRESULT hr = S_OK;
    LONG Status;
    HKEY hKeyRoot   = NULL;
    HKEY hKeyBucket = NULL;
    HKEY hKeyTags   = NULL;
    HKEY hKeyAux    = NULL;

    if (ERROR_SUCCESS != RegOpenKeyExA(
            HKEY_CURRENT_USER,
            SZIE30CERTCLIENTAUTH,
            0,                  // dwReserved
            KEY_READ,
            &hKeyRoot
            ))
        return S_OK;

    // Copy any existing certificates
    if (ERROR_SUCCESS == RegOpenKeyExA(
            hKeyRoot,
            SZIE30CERTBUCKET,
            0,                  // dwReserved
            KEY_READ,
            &hKeyBucket
        )               &&

        ERROR_SUCCESS == RegOpenKeyExA(
            hKeyRoot,
            SZIE30AUXINFO,
            0,                  // dwReserved
            KEY_READ,
            &hKeyAux
            )               &&

        ERROR_SUCCESS == RegOpenKeyExA(
            hKeyRoot,
            SZIE30TAGS,
            0,                  // dwReserved
            KEY_READ,
            &hKeyTags
            )) {

            DWORD   cValuesCert, cchMaxNameCert, cbMaxDataCert;
            DWORD   cValuesTag, cchMaxNameTag, cbMaxDataTag;
            DWORD   cValuesAux, cchMaxNameAux, cbMaxDataAux;
            LPSTR   szName = NULL;
            BYTE *pbDataCert = NULL;
            BYTE *pbDataAux = NULL;
            BYTE *pbDataTag = NULL;


            // see how many and how big the registry is
            if (ERROR_SUCCESS != RegQueryInfoKey(
                        hKeyBucket,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        &cValuesCert,
                        &cchMaxNameCert,
                        &cbMaxDataCert,
                        NULL,
                        NULL
                        )           ||
                ERROR_SUCCESS != RegQueryInfoKey(
                        hKeyTags,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        &cValuesTag,
                        &cchMaxNameTag,
                        &cbMaxDataTag,
                        NULL,
                        NULL
                        )           ||
                ERROR_SUCCESS != RegQueryInfoKey(
                        hKeyAux,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        NULL,
                        &cValuesAux,
                        &cchMaxNameAux,
                        &cbMaxDataAux,
                        NULL,
                        NULL
                        ))
            {
                hr = SignError();
                goto Return;
            }       
            else {
                // allocate the memory needed to read the reg
                szName = (LPSTR) HEAPALLOC(cchMaxNameCert + 1);
                pbDataCert = (BYTE *) HEAPALLOC(cbMaxDataCert);
                pbDataTag = (BYTE *) HEAPALLOC(cbMaxDataTag);
                pbDataAux = (BYTE *) HEAPALLOC(cbMaxDataAux);
                
                if (NULL == szName      ||
                    NULL == pbDataCert  ||
                    NULL == pbDataAux   ||
                    NULL == pbDataTag   )
                    hr = E_OUTOFMEMORY;
            }

        // enum the registry getting certs
        for (DWORD i = 0; SUCCEEDED(hr) && i < cValuesCert; i++ ) {

            DWORD dwType;
            BYTE *  pb;
            CRYPT_KEY_PROV_INFO   keyInfo;
            DWORD cchName = cchMaxNameCert + 1;
            DWORD cbDataCert = cbMaxDataCert;
            DWORD cbDataTag = cbMaxDataTag;
            DWORD cbDataAux = cbMaxDataAux;

            PCCERT_CONTEXT pCertContxt = NULL;

            // don't have to worry about errors, just skip
            // sliently just be cause there is an internal
            // error in the registry doesn't mean we should
            // get all upset about it.

            // get the cert
            if (RegEnumValueA(
                hKeyBucket,
                i,
                szName,
                &cchName,
                NULL,
                &dwType,
                pbDataCert,
                &cbDataCert
                ) == ERROR_SUCCESS      &&

                dwType == REG_BINARY    &&

            // get the cert context
            (pCertContxt = CertCreateCertificateContext(
                X509_ASN_ENCODING,
                pbDataCert,
                cbDataCert)) != NULL        &&

            // get the tag
            RegQueryValueExA(
                hKeyTags,
                szName,
                NULL,
                &dwType,
                pbDataTag,
                &cbDataTag) == ERROR_SUCCESS    &&

            // get the aux info
            RegQueryValueExA(
                hKeyAux,
                (LPTSTR) pbDataTag,
                NULL,
                &dwType,
                pbDataAux,
                &cbDataAux) == ERROR_SUCCESS ) {

                // aux info is
                // wszPurpose
                // wszProvider
                // wszKeySet
                // wszFilename
                // wszCredentials
                // dwProviderType
                // dwKeySpec

                pb = pbDataAux;
                memset(&keyInfo, 0, sizeof(CRYPT_KEY_PROV_INFO));

                // skip purpose, should be client auth
                pb += (lstrlenW((LPWSTR) pb) + 1) * sizeof(WCHAR);

                // get the provider
                keyInfo.pwszProvName = (LPWSTR) pb;
                pb += (lstrlenW((LPWSTR) pb) + 1) * sizeof(WCHAR);

                // get the container name
                keyInfo.pwszContainerName = (LPWSTR) pb;
                pb += (lstrlenW((LPWSTR) pb) + 1) * sizeof(WCHAR);

                // skip filename, should be '\0'
                pb += (lstrlenW((LPWSTR) pb) + 1) * sizeof(WCHAR);

                // skip credential, don't really know what it is?
                pb += (lstrlenW((LPWSTR) pb) + 1) * sizeof(WCHAR);

                // get the provider type
                keyInfo.dwProvType = *((DWORD *) pb);
                pb += sizeof(DWORD);

                // get the key spec
                keyInfo.dwKeySpec  = *((DWORD *) pb);

                // add the property to the certificate
                if( !CertSetCertificateContextProperty(
                    pCertContxt,
                    CERT_KEY_PROV_INFO_PROP_ID,
                    0,
                    &keyInfo)           ||

                !CertAddCertificateContextToStore(
                    hStore,
                    pCertContxt,
                    CERT_STORE_ADD_USE_EXISTING,
                    NULL                            // ppStoreContext
                    )) {

                    MessageBox(
                        NULL,
                        "Copy Certificate Failed",
                        NULL,
                        MB_OK);


                   hr = SignError();
                }
            }

            if(pCertContxt != NULL)
                CertFreeCertificateContext(pCertContxt);
        }

        if (szName)
            HEAPFREE(szName);
        if (pbDataCert)
            HEAPFREE(pbDataCert);
        if(pbDataAux)
            HEAPFREE(pbDataAux);
        if(pbDataTag)
            HEAPFREE(pbDataTag);
    }

Return:

    if(hKeyRoot != NULL)
        RegCloseKey(hKeyRoot);
    if(hKeyBucket != NULL)
        RegCloseKey(hKeyBucket);
    if(hKeyTags != NULL)
        RegCloseKey(hKeyTags);
    if(hKeyAux != NULL)
        RegCloseKey(hKeyAux);
    if (FAILED(hr))
        return hr;

    return hr;
}


// Return List is Null terminated
static HCERTSTORE * GetMyStoreList()
{
    int i;
    HCERTSTORE *phStoreList;
    if (NULL == (phStoreList = (HCERTSTORE *) ACAlloc(
            sizeof(HCERTSTORE) * (NMYSTORES + 1))))
        return NULL;
    memset(phStoreList, 0, sizeof(HCERTSTORE) * (NMYSTORES + 1));
    for (i = 0; i < NMYSTORES; i++) {
    if (NULL == (phStoreList[i] = CertOpenSystemStore(
        NULL,
                rgpszMyStore[i])))
            goto ErrorReturn;
    }
    goto CommonReturn;

ErrorReturn:
    for (i = 0; i < NMYSTORES; i++) {
        if (phStoreList[i])
            CertCloseStore(phStoreList[i], 0);
    }

    ACFree(phStoreList);
    phStoreList = NULL;

CommonReturn:
    return phStoreList;
}

static HCERTSTORE * GetCaStoreList()
{
    int i;
    int cStore;
    HCERTSTORE *phStoreList;
    if (NULL == (phStoreList = (HCERTSTORE *) ACAlloc(
            sizeof(HCERTSTORE) * (NCASTORES + 1))))
        return NULL;
    memset(phStoreList, 0, sizeof(HCERTSTORE) * (NCASTORES + 1));

    cStore = 0;
    for (i = 0; i < NCASTORES; i++) {
        DWORD dwFlags;

        dwFlags = rgCaStoreInfo[i].dwFlags | CERT_STORE_READONLY_FLAG;
        if (phStoreList[cStore] = CertOpenStore(
                CERT_STORE_PROV_SYSTEM_A,
                0,                          // dwEncodingType
                0,                          // hCryptProv
                dwFlags,
                (const void *) rgCaStoreInfo[i].pszStore
                ))
            cStore++;
    }
    return phStoreList;
}

// Find first Issuer match. Don't verify anything. Returns TRUE if an
// issuer was found. For a self-signed issuer returns TRUE with *ppIssuer
// set to NULL.
static BOOL GetIssuer(
    IN PCCERT_CONTEXT pSubject,
    IN HCERTSTORE *phCaStoreList,
    OUT PCCERT_CONTEXT *ppIssuer
    )
{
    BOOL fResult = FALSE;
    PCCERT_CONTEXT pIssuer = NULL;
    HCERTSTORE hStore;
    while (hStore = *phCaStoreList++) {
        DWORD dwFlags = 0;
        pIssuer = CertGetIssuerCertificateFromStore(
            hStore,
            pSubject,
            NULL,       // pPrevIssuer,
            &dwFlags
            );
        if (pIssuer || GetLastError() == CRYPT_E_SELF_SIGNED) {
            fResult = TRUE;
            break;
        }
    }

    *ppIssuer = pIssuer;
    return fResult;
}

//+-------------------------------------------------------------------------
// If issuer name matches any cert in the chain, return allocated
// chain info. Otherwise, return NULL.
//
// If pbEncodedIssuerName == NULL || cbEncodedIssuerName = 0, match any
// issuer.
//--------------------------------------------------------------------------
static PCHAIN_INFO CreateChainInfo(
    IN PCCERT_CONTEXT pCert,
    IN BYTE *pbEncodedIssuerName,
    IN DWORD cbEncodedIssuerName,
    IN HCERTSTORE *phCaStoreList,
    IN HCERTSTORE *phMyStoreList
    )
{
    BOOL fIssuerMatch = FALSE;
    DWORD cCert = 1;
    DWORD cbCert = 0;
    PCHAIN_INFO pChainInfo;
    if (NULL == (pChainInfo = (PCHAIN_INFO) ACAlloc(sizeof(CHAIN_INFO))))
        return NULL;
    memset(pChainInfo, 0, sizeof(CHAIN_INFO));
    pChainInfo->rgpCert[0] = CertDuplicateCertificateContext(pCert);

    if (pbEncodedIssuerName == NULL)
        cbEncodedIssuerName = 0;

    while (pCert) {
        PCCERT_CONTEXT pIssuer;
        cbCert += ALIGN_LEN(pCert->cbCertEncoded);
        if (!fIssuerMatch) {
            if (cbEncodedIssuerName == 0 ||
                (cbEncodedIssuerName == pCert->pCertInfo->Issuer.cbData &&
                    memcmp(pbEncodedIssuerName,
                        pCert->pCertInfo->Issuer.pbData,
                        cbEncodedIssuerName) == 0))
                fIssuerMatch = TRUE;
        }
        if (GetIssuer(pCert, phCaStoreList, &pIssuer) ||
                GetIssuer(pCert, phMyStoreList, &pIssuer)) {
            pCert = pIssuer;
            if (pCert) {
                assert (cCert < MAX_CHAIN_LEN);
                if (cCert < MAX_CHAIN_LEN)
                    pChainInfo->rgpCert[cCert++] = pCert;
                else {
                    CertFreeCertificateContext(pCert);
                    pCert = NULL;
                }
            }
            // else
            //  Self-signed
        }
        else
            pCert = NULL;
    }

    if (fIssuerMatch) {
        pChainInfo->cCert = cCert;
        pChainInfo->cbCert = cbCert;
        return pChainInfo;
    } else {
        while (cCert--)
            CertFreeCertificateContext(pChainInfo->rgpCert[cCert]);
        ACFree(pChainInfo);
        return NULL;
    }
}

//+-------------------------------------------------------------------------
//  Check if the certificate has key provider information.
//  If dwKeySpec != 0, also check that the provider's public key matches the
//  public key in the certificate.
//--------------------------------------------------------------------------
static BOOL CheckKeyProvInfo(
    IN PCCERT_CONTEXT pCert,
    IN DWORD dwKeySpec,
    OUT DWORD *pcbKeyProvInfo
    )
{
    BOOL fResult = FALSE;
    HCRYPTPROV hCryptProv = 0;
    PCRYPT_KEY_PROV_INFO pKeyProvInfo = NULL;
    DWORD cbKeyProvInfo;
    DWORD cbData;
    PCERT_PUBLIC_KEY_INFO pPubKeyInfo = NULL;
    DWORD cbPubKeyInfo;

    cbKeyProvInfo = 0;
    CertGetCertificateContextProperty(
        pCert,
        CERT_KEY_PROV_INFO_PROP_ID,
        NULL,                           // pvData
        &cbKeyProvInfo
        );
    if (cbKeyProvInfo) {
        if (dwKeySpec == 0)
            fResult = TRUE;
        else {
            DWORD dwIdx;
            if (NULL == (pKeyProvInfo = (PCRYPT_KEY_PROV_INFO) ACAlloc(cbKeyProvInfo)))
                goto CommonReturn;
            if (!CertGetCertificateContextProperty(
                    pCert,
                    CERT_KEY_PROV_INFO_PROP_ID,
                    pKeyProvInfo,
                    &cbKeyProvInfo
                    )) goto CommonReturn;
            if (!CryptAcquireContextU(
                    &hCryptProv,
                    pKeyProvInfo->pwszContainerName,
                    pKeyProvInfo->pwszProvName,
                    pKeyProvInfo->dwProvType,
                    pKeyProvInfo->dwFlags & ~CERT_SET_KEY_PROV_HANDLE_PROP_ID
                    )) {
                hCryptProv = NULL;
                goto CommonReturn;
            }
            for (dwIdx = 0; dwIdx < pKeyProvInfo->cProvParam; dwIdx++) {
                PCRYPT_KEY_PROV_PARAM pKeyProvParam =
                    &pKeyProvInfo->rgProvParam[dwIdx];
                if (!CryptSetProvParam(
                        hCryptProv,
                        pKeyProvParam->dwParam,
                        pKeyProvParam->pbData,
                        pKeyProvParam->dwFlags
                        )) goto CommonReturn;
            }

            // Get public key to compare certificate with
            cbPubKeyInfo = 0;
            CryptExportPublicKeyInfo(
                hCryptProv,
                dwKeySpec,
                pCert->dwCertEncodingType,
                NULL,               // pPubKeyInfo
                &cbPubKeyInfo
                );
            if (cbPubKeyInfo == 0) goto CommonReturn;
            if (NULL == (pPubKeyInfo = (PCERT_PUBLIC_KEY_INFO) ACAlloc(
                    cbPubKeyInfo)))
                goto CommonReturn;
            if (!CryptExportPublicKeyInfo(
                    hCryptProv,
                    dwKeySpec,
                    pCert->dwCertEncodingType,
                    pPubKeyInfo,
                    &cbPubKeyInfo
                    )) goto CommonReturn;
            fResult = CertComparePublicKeyInfo(
                    pCert->dwCertEncodingType,
                    &pCert->pCertInfo->SubjectPublicKeyInfo,
                    pPubKeyInfo);
        }
    }
CommonReturn:
    if (hCryptProv) {
        DWORD dwErr = GetLastError();
        CryptReleaseContext(hCryptProv, 0);
        SetLastError(dwErr);
    }
    if (pKeyProvInfo)
        ACFree(pKeyProvInfo);
    if (pPubKeyInfo)
        ACFree(pPubKeyInfo);
    *pcbKeyProvInfo = cbKeyProvInfo;
    return fResult;
}


//+-------------------------------------------------------------------------
//  Find all certificate chains tying the given issuer name to any certificate
//  that the current user has a private key for.
//
//  If pbEncodedIssuerName == NULL || cbEncodedIssuerName = 0, match any
//  issuer.
//--------------------------------------------------------------------------
HRESULT
WINAPI
FindCertsByIssuer(
    OUT PCERT_CHAIN pCertChains,
    IN OUT DWORD *pcbCertChains,
    OUT DWORD *pcCertChains,        // count of certificates chains returned
    IN BYTE* pbEncodedIssuerName,   // DER encoded issuer name
    IN DWORD cbEncodedIssuerName,   // count in bytes of encoded issuer name
    IN LPCWSTR pwszPurpose,         // "ClientAuth" or "CodeSigning"
    IN DWORD dwKeySpec              // only return signers supporting this
                                    // keyspec

    )
{
    HRESULT hr;
    HCERTSTORE *phMyStoreList = NULL;
    HCERTSTORE *phCaStoreList = NULL;
    HCERTSTORE *phStore;
    HCERTSTORE hStore;

    DWORD cChain = 0;
    DWORD cbChain;
    DWORD cTotalCert = 0;
    PCHAIN_INFO pChainInfoHead = NULL;
    LONG cbExtra = 0;

    // get the certs out of the IE30 tree and put it in ours
    // open the IE30 store

    if (NULL != (hStore = CertOpenSystemStore(
    NULL,
    IE30CONVERTEDSTORE))) {

    // don't care about errors, and we don't
    // want to delete the old store just yet.
    GetAndIe30ClientAuthCertificates(hStore);
    CertCloseStore(hStore, 0);
    }


    // copy the IE30 certs


    if (NULL == (phMyStoreList = GetMyStoreList()))
        goto ErrorReturn;
    if (NULL == (phCaStoreList = GetCaStoreList()))
        goto ErrorReturn;

    // Iterate through all "My" cert stores to find certificates having a
    // CRYPT_KEY_PROV_INFO property
    phStore = phMyStoreList;
    while (hStore = *phStore++) {
        PCCERT_CONTEXT pCert = NULL;
        while (pCert = CertEnumCertificatesInStore(hStore, pCert)) {
            DWORD cbKeyProvInfo;
            if (CheckKeyProvInfo(pCert, dwKeySpec, &cbKeyProvInfo)) {
                // Create a cert chain and check for an issuer name match
                // of any cert in the chain.
                PCHAIN_INFO pChainInfo;
                if (pChainInfo = CreateChainInfo(
                        pCert,
                        pbEncodedIssuerName,
                        cbEncodedIssuerName,
                        phCaStoreList,
                        phMyStoreList
                        )) {
                    // Add to list of chains
                    pChainInfo->pNext = pChainInfoHead;
                    pChainInfoHead = pChainInfo;

                    // Update bytes needed for KeyProvInfo
                    pChainInfo->cbKeyProvInfo = ALIGN_LEN(cbKeyProvInfo);

                    // Update totals
                    cbExtra += pChainInfo->cbKeyProvInfo + pChainInfo->cbCert;
                    cChain++;
                    cTotalCert += pChainInfo->cCert;
                }
            }
        }
    }

    cbChain = sizeof(CERT_CHAIN) * cChain +
        sizeof(CERT_BLOB) * cTotalCert + cbExtra;

    {
        // Check and update output lengths and counts
        DWORD cbIn;

        if (cChain == 0) {
            hr = CRYPT_E_NOT_FOUND;
            goto HrError;
        }
        if (pCertChains == NULL)
            *pcbCertChains = 0;
        cbIn = *pcbCertChains;
        *pcCertChains = cChain;
        *pcbCertChains = cbChain;

        if (cbIn == 0) {
            hr = S_OK;
            goto CommonReturn;
        } else if (cbIn < cbChain) {
            hr = HRESULT_FROM_WIN32(ERROR_BAD_LENGTH);
            goto CommonReturn;
        }
    }

    {
        // Copy cert chains to output

        PCERT_CHAIN pOutChain;
        PCERT_BLOB pCertBlob;
        BYTE *pbExtra;
        PCHAIN_INFO pChainInfo;

        pOutChain = pCertChains;
        pCertBlob = (PCERT_BLOB) (((BYTE *) pOutChain) +
            sizeof(CERT_CHAIN) * cChain);
        pbExtra = ((BYTE *) pCertBlob) + sizeof(CERT_BLOB) * cTotalCert;
        pChainInfo = pChainInfoHead;
        for ( ;  pChainInfo != NULL;
                                pChainInfo = pChainInfo->pNext, pOutChain++) {
            DWORD cb;
            DWORD cCert = pChainInfo->cCert;
            PCCERT_CONTEXT *ppCert = pChainInfo->rgpCert;
    
            pOutChain->cCerts = cCert;
            pOutChain->certs = pCertBlob;
            cb = pChainInfo->cbKeyProvInfo;
            cbExtra -= cb;
            assert(cbExtra >= 0);
            if (cbExtra < 0) goto UnexpectedError;
            if (!CertGetCertificateContextProperty(
                    *ppCert,
                    CERT_KEY_PROV_INFO_PROP_ID,
                    pbExtra,
                    &cb
                    ))
                goto UnexpectedError;
            pOutChain->keyLocatorInfo = * ((PCRYPT_KEY_PROV_INFO) pbExtra);
            pbExtra += pChainInfo->cbKeyProvInfo;
    
            for ( ; cCert > 0; cCert--, ppCert++, pCertBlob++) {
                cb = (*ppCert)->cbCertEncoded;
                cbExtra -= ALIGN_LEN(cb);
                assert(cbExtra >= 0);
                if (cbExtra < 0) goto UnexpectedError;

                pCertBlob->cbData = cb;
                pCertBlob->pbData = pbExtra;
                memcpy(pbExtra, (*ppCert)->pbCertEncoded, cb);
                pbExtra += ALIGN_LEN(cb);
            }
        }
        assert(cbExtra == 0);
        assert(pCertBlob == (PCERT_BLOB) ((BYTE *) pCertChains +
            sizeof(CERT_CHAIN) * cChain +
            sizeof(CERT_BLOB) * cTotalCert));
    }

    hr = S_OK;
    goto CommonReturn;

UnexpectedError:
    hr = E_UNEXPECTED;
    goto HrError;
ErrorReturn:
    hr = SignError();
HrError:
    *pcbCertChains = 0;
    *pcCertChains = 0;
CommonReturn:
    while (pChainInfoHead) {
        PCHAIN_INFO pChainInfo = pChainInfoHead;
        DWORD cCert = pChainInfo->cCert;
        while (cCert--)
            CertFreeCertificateContext(pChainInfo->rgpCert[cCert]);
        pChainInfoHead = pChainInfo->pNext;
        ACFree(pChainInfo);
    }

    if (phMyStoreList) {
        phStore = phMyStoreList;
        while (hStore = *phStore++)
            CertCloseStore(hStore, 0);
        ACFree(phMyStoreList);
    }
    if (phCaStoreList) {
        phStore = phCaStoreList;
        while (hStore = *phStore++)
            CertCloseStore(hStore, 0);
        ACFree(phCaStoreList);
    }

    return hr;
}