//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1995 - 1999
//
//  File:       store.cpp
//
//--------------------------------------------------------------------------

#include <pch.cpp>

#pragma hdrstop

#include <setupapi.h>
#include "ocmanage.h"
#include "initcert.h"
#include "cscsp.h"
#include "csber.h"


DWORD
cuGetSystemStoreFlags()
{
    return(g_fEnterpriseRegistry?
	    CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE :
	    (g_fUserRegistry?
	     CERT_SYSTEM_STORE_CURRENT_USER :
	     CERT_SYSTEM_STORE_LOCAL_MACHINE));
}


// Parse a CertIndex -- any one of the following:
//
// Return the following in *piCert, *piCRL and *piCTL.  MAXDWORD if not
// specified
// Each value must be less than 64k.
//  iCert	decimal number
//  iCert.iCRL	decimal number, period, decimal number
//  .iCRL	period, decimal number
//  ..iCTL	period, period, decimal number
//
// Return the string in *ppwszCertName, if no Cert, CRL and CTL indexes.

HRESULT
ParseCertCRLIndex(
    IN WCHAR const *pwszCertIndex,
    OUT WCHAR **ppwszCertName,
    OUT DWORD *piCert,
    OUT DWORD *piCRL,
    OUT DWORD *piCTL)
{
    HRESULT hr;
    WCHAR *pwszCopy = NULL;
    
    *ppwszCertName = NULL;
    *piCert = MAXDWORD;
    *piCRL = MAXDWORD;
    *piCTL = MAXDWORD;
    if (NULL != pwszCertIndex && 0 != lstrcmp(L"*", pwszCertIndex))
    {
	BOOL fNumericIndex = TRUE;
	WCHAR *pwszCert;
	WCHAR *pwszCRL;
	WCHAR *pwszCTL;
	
	if (L' ' == *pwszCertIndex)
	{
	    fNumericIndex = FALSE;
	    pwszCertIndex++;
	}
	hr = myDupString(pwszCertIndex, &pwszCopy);
	_JumpIfError(hr, error, "myDupString");

	pwszCert = pwszCopy;

	if (!iswdigit(*pwszCert) && L'.' != *pwszCert)
	{
	    fNumericIndex = FALSE;
	}

	pwszCRL = NULL;
	pwszCTL = NULL;
	if (fNumericIndex)
	{
	    pwszCRL = wcschr(pwszCert, L'.');
	    if (NULL != pwszCRL)
	    {
		*pwszCRL++ = L'\0';
		pwszCTL = wcschr(pwszCRL, L'.');
		if (NULL != pwszCTL)
		{
		    *pwszCTL++ = L'\0';
		    if (L'\0' != *pwszCTL)
		    {
			hr = cuGetLong(pwszCTL, (LONG *) piCTL);
			if (S_OK != hr || 64*1024 <= *piCTL)
			{
			    fNumericIndex = FALSE;
			}
		    }
		}
		if (fNumericIndex && L'\0' != *pwszCRL)
		{
		    hr = cuGetLong(pwszCRL, (LONG *) piCRL);
		    if (S_OK != hr || 64*1024 <= *piCRL)
		    {
			fNumericIndex = FALSE;
		    }
		}
	    }
	}
	if (fNumericIndex && L'\0' != *pwszCert)
	{
	    hr = cuGetLong(pwszCert, (LONG *) piCert);
	    if (S_OK != hr || 64*1024 <= *piCert)
	    {
		fNumericIndex = FALSE;
	    }
	}
	if (!fNumericIndex)
	{
	    hr = myRevertSanitizeName(pwszCertIndex, ppwszCertName);
	    _JumpIfError(hr, error, "myRevertSanitizeName");

	    *piCert = MAXDWORD;
	    *piCRL = MAXDWORD;
	    *piCTL = MAXDWORD;
	}
    }
    if (g_fVerbose)
    {
	wprintf(
	    L"pwszCertIndex=%ws, Name=%ws idx=%d.%d.%d\n",
	    pwszCertIndex,
	    *ppwszCertName,
	    *piCert,
	    *piCRL,
	    *piCTL);
    }
    hr = S_OK;

error:
    if (NULL != pwszCopy)
    {
	LocalFree(pwszCopy);
    }
    return(hr);
}


HRESULT
SavePFXStoreToFile(
    IN HCERTSTORE hStorePFX,
    IN WCHAR const *pwszfnOut,
    IN WCHAR const *pwszPassword,
    IN OUT WCHAR **ppwszPassword)
{
    HRESULT hr;
    CRYPT_DATA_BLOB pfx;
    WCHAR wszPassword[MAX_PATH];

    pfx.pbData = NULL;

    if (NULL == *ppwszPassword)
    {
	if (NULL == pwszPassword || 0 == wcscmp(L"*", pwszPassword))
	{
	    wprintf(L"Enter new password for output file %ws:\n", pwszfnOut);
	    hr = cuGetPassword(TRUE, wszPassword, ARRAYSIZE(wszPassword));
	    _JumpIfError(hr, error, "cuGetPassword");

	    pwszPassword = wszPassword;
	}
	hr = myDupString(pwszPassword, ppwszPassword);
	_JumpIfError(hr, error, "myDupString");
    }
    pwszPassword = *ppwszPassword;

    // GemPlus returns NTE_BAD_TYPE instead of NTE_BAD_KEY, blowing up
    // REPORT_NOT_ABLE* filtering.  if they ever get this right, we can pass
    // "[...] : EXPORT_PRIVATE_KEYS"

    hr = myPFXExportCertStore(
		hStorePFX,
		&pfx,
		pwszPassword,
		EXPORT_PRIVATE_KEYS | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY);
    _JumpIfError(hr, error, "myPFXExportCertStore");

    hr = EncodeToFileW(
		pwszfnOut,
		pfx.pbData,
		pfx.cbData,
		CRYPT_STRING_BINARY | g_EncodeFlags);
    _JumpIfError(hr, error, "EncodeToFileW");

error:
    if (NULL != pfx.pbData)
    {
	LocalFree(pfx.pbData);
    }
    return(hr);
}


HRESULT
SavePFXToFile(
    IN CERT_CONTEXT const *pCert,
    IN WCHAR const *pwszfnOut,
    IN BOOL fFirst,
    IN WCHAR const *pwszPassword,
    IN OUT WCHAR **ppwszPassword)
{
    HRESULT hr;
    HCERTSTORE hTempMemoryStore = NULL;

    hTempMemoryStore = CertOpenStore(
				CERT_STORE_PROV_MEMORY,
				X509_ASN_ENCODING,
				NULL,
				CERT_STORE_NO_CRYPT_RELEASE_FLAG |
				    CERT_STORE_ENUM_ARCHIVED_FLAG,
				NULL);
    if (NULL == hTempMemoryStore)
    {
        hr = myHLastError();
        _JumpError(hr, error, "CertOpenStore");
    }

    // Begin Chain Building

    hr = myAddChainToMemoryStore(hTempMemoryStore, pCert);
    _JumpIfError(hr, error, "myAddChainToMemoryStore");

    // End Chain Building

    hr = SavePFXStoreToFile(
			hTempMemoryStore,
			pwszfnOut,
			pwszPassword,
			ppwszPassword);
    _JumpIfError(hr, error, "SavePFXStoreToFile");

error:
    if (NULL != hTempMemoryStore)
    {
	CertCloseStore(hTempMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    return(hr);
}


HRESULT
SavePVKToFile(
    IN CERT_CONTEXT const *pCert,
    IN WCHAR const *pwszfnOut,
    IN BOOL fFirst)
{
    return(S_OK);
}


HRESULT
cuDumpCTLProperties(
    IN CTL_CONTEXT const *pCTL)
{
    HRESULT hr;
    DWORD dwPropId;
    BYTE *pb = NULL;
    DWORD cb;

    dwPropId = 0;
    while (TRUE)
    {
	if (NULL != pb)
	{
	    LocalFree(pb);
	    pb = NULL;
	}
	dwPropId = CertEnumCTLContextProperties(pCTL, dwPropId);
	if (0 == dwPropId)
	{
	    break;
	}
	while (TRUE)
	{
	    if (!CertGetCTLContextProperty(pCTL, dwPropId, pb, &cb))
	    {
		hr = myHLastError();
		_JumpError(hr, error, "CertGetCTLContextProperty");
	    }
	    if (NULL != pb)
	    {
		break;		// memory alloc'd, property fetched
	    }
	    pb = (BYTE *) LocalAlloc(LMEM_FIXED, cb);
	    if (NULL == pb)
	    {
		hr = E_OUTOFMEMORY;
		_JumpError(hr, error, "LocalAlloc");
	    }
	}
	hr = cuDumpFormattedProperty(dwPropId, NULL, pb, cb);
	_PrintIfError(hr, "cuDumpFormattedProperty");
    }
    hr = S_OK;

error:
    if (NULL != pb)
    {
	LocalFree(pb);
    }
    return(hr);
}


HRESULT
cuDumpCRLProperties(
    IN CRL_CONTEXT const *pCRL)
{
    HRESULT hr;
    DWORD dwPropId;
    BYTE *pb = NULL;
    DWORD cb;

    dwPropId = 0;
    while (TRUE)
    {
	if (NULL != pb)
	{
	    LocalFree(pb);
	    pb = NULL;
	}
	dwPropId = CertEnumCRLContextProperties(pCRL, dwPropId);
	if (0 == dwPropId)
	{
	    break;
	}
	while (TRUE)
	{
	    if (!CertGetCRLContextProperty(pCRL, dwPropId, pb, &cb))
	    {
		hr = myHLastError();
		_JumpError(hr, error, "CertGetCRLContextProperty");
	    }
	    if (NULL != pb)
	    {
		break;		// memory alloc'd, property fetched
	    }
	    pb = (BYTE *) LocalAlloc(LMEM_FIXED, cb);
	    if (NULL == pb)
	    {
		hr = E_OUTOFMEMORY;
		_JumpError(hr, error, "LocalAlloc");
	    }
	}
	hr = cuDumpFormattedProperty(dwPropId, NULL, pb, cb);
	_PrintIfError(hr, "cuDumpFormattedProperty");
    }
    hr = S_OK;

error:
    if (NULL != pb)
    {
	LocalFree(pb);
    }
    return(hr);
}


HRESULT
cuDumpCertProperties(
    IN CERT_CONTEXT const *pCert)
{
    HRESULT hr;
    DWORD dwPropId;
    BYTE *pb = NULL;
    DWORD cb;

    dwPropId = 0;
    while (TRUE)
    {
	dwPropId = CertEnumCertificateContextProperties(pCert, dwPropId);
	if (0 == dwPropId)
	{
	    break;
	}
	if (NULL != pb)
	{
	    LocalFree(pb);
	    pb = NULL;
	}
	while (TRUE)
	{
	    if (!CertGetCertificateContextProperty(pCert, dwPropId, pb, &cb))
	    {
		hr = myHLastError();
		_JumpError(hr, error, "CertGetCertificateContextProperty");
	    }
	    if (NULL != pb)
	    {
		break;		// memory alloc'd, property fetched
	    }
	    pb = (BYTE *) LocalAlloc(LMEM_FIXED, cb);
	    if (NULL == pb)
	    {
		hr = E_OUTOFMEMORY;
		_JumpError(hr, error, "LocalAlloc");
	    }
	}
	hr = cuDumpFormattedProperty(dwPropId, NULL, pb, cb);
	_PrintIfError(hr, "cuDumpFormattedProperty");
    }
    hr = S_OK;

error:
    if (NULL != pb)
    {
	LocalFree(pb);
    }
    return(hr);
}


HRESULT
EnumCertsInStore(
    IN HCERTSTORE hStore,
    IN DWORD Mode,
    IN DWORD iCertSave,
    OPTIONAL IN WCHAR const *pwszCertName,
    IN DWORD cbHash,
    OPTIONAL IN BYTE *pbHash,
    OPTIONAL IN WCHAR const *pwszfnOut,
    OPTIONAL IN WCHAR const *pwszPasswordArg,
    IN OUT WCHAR **ppwszPassword,
    OUT DWORD *pcCert)
{
    HRESULT hr;
    HRESULT hr2;
    DWORD iCert;
    CERT_CONTEXT const *pCert = NULL;
    BSTR strSerialNumber = NULL;

    *pcCert = 0;
    hr2 = S_OK;
    if (NULL != pwszCertName)
    {
	hr = myMakeSerialBstr(pwszCertName, &strSerialNumber);
	_PrintIfError2(hr, "myMakeSerialBstr", hr);
    }
    for (iCert = 0; ; iCert++)
    {
	DWORD VerifyState;
	BOOL fSigningKey;
	BOOL fMatchingKey;

	pCert = CertEnumCertificatesInStore(hStore, pCert);
	if (NULL == pCert)
	{
	    break;
	}
	if (MAXDWORD == iCertSave || iCert == iCertSave)
	{
	    DWORD cb;

	    if (NULL != pwszCertName)
	    {
		BOOL fMatch;
		
		hr = myCertMatch(
			pCert,
			pwszCertName,
			NULL == strSerialNumber &&	// fAllowMissingCN
			    NULL == pbHash,
			pbHash,
			cbHash,
			strSerialNumber,
			&fMatch);
		_PrintIfError(hr, "myCertMatch");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
		if (S_OK != hr || !fMatch)
		{
		    continue;
		}
	    }
	    if (0 != *pcCert)
	    {
		wprintf(wszNewLine);
	    }

	    wprintf(
		myLoadResourceString(IDS_FORMAT_DUMP_CERT_INDEX),  // "================ Certificate %d ================"
		iCert);
	    wprintf(wszNewLine);

	    if (CertGetCertificateContextProperty(
					    pCert,
					    CERT_ARCHIVED_PROP_ID,
					    NULL,
					    &cb))
	    {
		wprintf(myLoadResourceString(IDS_ARCHIVED)); // "Archived!"
		wprintf(wszNewLine);
	    }
	    if (iCert == iCertSave &&
		NULL != pwszfnOut &&
		(DVNS_SAVECERT & Mode))
	    {
		hr = EncodeToFileW(
			    pwszfnOut,
			    pCert->pbCertEncoded,
			    pCert->cbCertEncoded,
			    CRYPT_STRING_BINARY | g_EncodeFlags);
		_PrintIfError(hr, "EncodeToFileW");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
	    }

	    hr = cuDumpAsnBinary(
			    pCert->pbCertEncoded,
			    pCert->cbCertEncoded,
			    MAXDWORD);
	    _PrintIfError(hr, "cuDumpAsnBinary");
	    if (S_OK == hr2)
	    {
		hr2 = hr;
	    }

	    if (DVNS_REPAIRKPI & Mode)
	    {
		if (!CryptFindCertificateKeyProvInfo(
					    pCert,
					    0,	// dwFlags
					    NULL))	// pvReserved
		{
		    hr = myHLastError();
		    _PrintError(hr, "CryptFindCertificateKeyProvInfo");
		    if (S_OK == hr2)
		    {
			hr2 = hr;
		    }
		}
	    
	    }
	    if ((DVNS_DUMPPROPERTIES & Mode) && !g_fQuiet)
	    {
		hr = cuDumpCertProperties(pCert);
		_PrintIfError(hr, "cuDumpCertProperties");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
	    }
	    if (DVNS_DUMPKEYS & Mode)
	    {
		if (0 == (DVNS_DUMPPROPERTIES & Mode) || g_fQuiet)
		{
		    hr = cuDumpCertKeyProviderInfo(
					    g_wszPad2,
					    pCert,
					    NULL,
					    NULL);
		    _PrintIfError(hr, "cuDumpCertKeyProviderInfo");
		    if (S_OK == hr2)
		    {
			hr2 = hr;
		    }
		}
		hr = cuDumpPrivateKey(pCert, &fSigningKey, &fMatchingKey);
		if (!IsHrSkipPrivateKey(hr))
		{
		    if (S_OK != hr)
		    {
			wprintf(myLoadResourceString(
			    fSigningKey?
				IDS_SIGNATURE_BAD :   // "Signature test FAILED"
				IDS_ENCRYPTION_BAD)); // "Encryption test FAILED"
			wprintf(wszNewLine);
			_PrintError(hr, "cuDumpPrivateKey");
			fMatchingKey = FALSE;
		    }

		    if (fMatchingKey)
		    {
			wprintf(myLoadResourceString(
			    fSigningKey?
				IDS_SIGNATURE_OK :   // "Signature test passed"
				IDS_ENCRYPTION_OK)); // "Encryption test passed"
			wprintf(wszNewLine);
		    }
		}
	    }
	    if (DVNS_VERIFYCERT & Mode)
	    {
		hr = cuVerifyCertContext(
				pCert,
				(DVNS_CASTORE & Mode)? hStore : NULL,
				NULL,
				0,
				&VerifyState);
		if (S_OK != hr)
		{
		    cuPrintError(IDS_ERR_FORMAT_BAD_CERT, hr);
		    _PrintError(hr, "cuVerifyCertContext");
		    if (S_OK == hr2)
		    {
			hr2 = hr;		// Save first error
		    }
		}
		else if (0 == (VS_ERRORMASK & VerifyState))
		{
		    wprintf(myLoadResourceString(IDS_CERT_VERIFIES)); // "Certificate is valid"
		}
		wprintf(wszNewLine);
	    }
	    if (DVNS_SAVEPFX & Mode)
	    {
		hr = SavePFXToFile(
				pCert,
				pwszfnOut,
				0 == *pcCert,
				pwszPasswordArg,
				ppwszPassword);
		_PrintIfError(hr, "SavePFXToFile");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
	    }
	    if (DVNS_SAVEPVK & Mode)
	    {
		hr = SavePVKToFile(pCert, pwszfnOut, 0 == *pcCert);
		_PrintIfError(hr, "SavePVKToFile");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
	    }
	    (*pcCert)++;
	}
    }
    hr = hr2;
    _JumpIfError(hr, error, "EnumCertsInStore");

error:
    if (NULL != pCert)
    {
	CertFreeCertificateContext(pCert);
    }
    if (NULL != strSerialNumber)
    {
	SysFreeString(strSerialNumber);
    }
    return(hr);
}


HRESULT
EnumCRLsInStore(
    IN HCERTSTORE hStore,
    IN DWORD Mode,
    IN DWORD iCRLSave,
    OPTIONAL IN WCHAR const *pwszCertName,
    IN DWORD cbHash,
    OPTIONAL IN BYTE *pbHash,
    OPTIONAL IN WCHAR const *pwszfnOut,
    OUT DWORD *pcCRL)
{
    HRESULT hr;
    HRESULT hr2;
    DWORD iCRL;
    CRL_CONTEXT const *pCRL = NULL;

    *pcCRL = 0;
    hr2 = S_OK;
    for (iCRL = 0; ; iCRL++)
    {
	pCRL = CertEnumCRLsInStore(hStore, pCRL);
	if (NULL == pCRL)
	{
	    break;
	}
	if (MAXDWORD == iCRLSave || iCRL == iCRLSave)
	{
	    if (NULL != pwszCertName)
	    {
		BOOL fMatch;
		
		hr = myCRLMatch(
			    pCRL,
			    pwszCertName,
			    TRUE,		// fAllowMissingCN
			    pbHash,
			    cbHash,
			    &fMatch);
		_PrintIfError(hr, "myCRLMatch");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
		if (S_OK != hr || !fMatch)
		{
		    continue;
		}
	    }
	    if (0 != *pcCRL)
	    {
		wprintf(wszNewLine);
	    }

	    wprintf(
		myLoadResourceString(IDS_FORMAT_DUMP_CRL_INDEX),  // "================ CRL %d ================"
		iCRL);
	    wprintf(wszNewLine);

	    if (iCRL == iCRLSave &&
		NULL != pwszfnOut &&
		(DVNS_SAVECRL & Mode))
	    {
		hr = EncodeToFileW(
			    pwszfnOut,
			    pCRL->pbCrlEncoded,
			    pCRL->cbCrlEncoded,
			    CRYPT_STRING_BINARY | g_EncodeFlags);
		_PrintIfError(hr, "EncodeToFileW");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
	    }

	    hr = cuDumpAsnBinary(
			    pCRL->pbCrlEncoded,
			    pCRL->cbCrlEncoded,
			    MAXDWORD);
	    _PrintIfError(hr, "cuDumpAsnBinary");
	    if (S_OK == hr2)
	    {
		hr2 = hr;
	    }

	    if ((DVNS_DUMPPROPERTIES & Mode) && !g_fQuiet)
	    {
		hr = cuDumpCRLProperties(pCRL);
		_PrintIfError(hr, "cuDumpCRLProperties");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
	    }
	    (*pcCRL)++;
	}
    }
    hr = hr2;
    _JumpIfError(hr, error, "EnumCRLsInStore");

error:
    if (NULL != pCRL)
    {
	CertFreeCRLContext(pCRL);
    }
    return(hr);
}


HRESULT
EnumCTLsInStore(
    IN HCERTSTORE hStore,
    IN DWORD Mode,
    IN DWORD iCTLSave,
    OPTIONAL IN WCHAR const *pwszCertName,
    IN DWORD cbHash,
    OPTIONAL IN BYTE *pbHash,
    OPTIONAL IN WCHAR const *pwszfnOut,
    OUT DWORD *pcCTL)
{
    HRESULT hr;
    HRESULT hr2;
    DWORD iCTL;
    CTL_CONTEXT const *pCTL = NULL;

    *pcCTL = 0;
    hr2 = S_OK;
    for (iCTL = 0; ; iCTL++)
    {
	pCTL = CertEnumCTLsInStore(hStore, pCTL);
	if (NULL == pCTL)
	{
	    break;
	}
	if (MAXDWORD == iCTLSave || iCTL == iCTLSave)
	{
	    DWORD cb;

	    if (NULL != pwszCertName)
	    {
		BOOL fMatch;
		
		hr = myCTLMatch(pCTL, pbHash, cbHash, &fMatch);
		_PrintIfError(hr, "myCTLMatch");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
		if (S_OK != hr || !fMatch)
		{
		    continue;
		}
	    }
	    if (0 != *pcCTL)
	    {
		wprintf(wszNewLine);
	    }

	    wprintf(
		myLoadResourceString(IDS_FORMAT_DUMP_CTL_INDEX),  // "================ CTL %d ================"
		iCTL);
	    wprintf(wszNewLine);

	    if (CertGetCTLContextProperty(
				    pCTL,
				    CERT_ARCHIVED_PROP_ID,
				    NULL,
				    &cb))
	    {
		wprintf(myLoadResourceString(IDS_ARCHIVED)); // "Archived!"
		wprintf(wszNewLine);
	    }
	    if (iCTL == iCTLSave &&
		NULL != pwszfnOut &&
		(DVNS_SAVECTL & Mode))
	    {
		hr = EncodeToFileW(
			    pwszfnOut,
			    pCTL->pbCtlEncoded,
			    pCTL->cbCtlEncoded,
			    CRYPT_STRING_BINARY | g_EncodeFlags);
		_PrintIfError(hr, "EncodeToFileW");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
	    }

	    hr = cuDumpAsnBinary(
			    pCTL->pbCtlEncoded,
			    pCTL->cbCtlEncoded,
			    MAXDWORD);
	    _PrintIfError(hr, "cuDumpAsnBinary");
	    if (S_OK == hr2)
	    {
		hr2 = hr;
	    }

	    if ((DVNS_DUMPPROPERTIES & Mode) && !g_fQuiet)
	    {
		hr = cuDumpCTLProperties(pCTL);
		_PrintIfError(hr, "cuDumpCTLProperties");
		if (S_OK == hr2)
		{
		    hr2 = hr;
		}
	    }
#if 0
	    if (DVNS_VERIFYCERT & Mode)
	    {
		hr = cuVerifyCertContext(
				pCTL,
				(DVNS_CASTORE & Mode)? hStore : NULL,
				NULL,
				0,
				&VerifyState);
		if (S_OK != hr)
		{
		    cuPrintError(IDS_ERR_FORMAT_BAD_CTL, hr);
		    _PrintError(hr, "cuVerifyCertContext");
		    if (S_OK == hr2)
		    {
			hr2 = hr;		// Save first error
		    }
		}
		else
		{
		    wprintf(myLoadResourceString(IDS_CTL_VERIFIES)); // "CTL is valid"
		}
		wprintf(wszNewLine);
	    }
#endif
	    (*pcCTL)++;
	}
    }
    hr = hr2;
    _JumpIfError(hr, error, "EnumCTLsInStore");

error:
    if (NULL != pCTL)
    {
	CertFreeCTLContext(pCTL);
    }
    return(hr);
}


HRESULT
cuDumpAndVerifyStore(
    IN HCERTSTORE hStore,
    IN DWORD Mode,
    OPTIONAL IN WCHAR const *pwszCertName,
    IN DWORD iCertSave,
    IN DWORD iCRLSave,
    IN DWORD iCTLSave,
    OPTIONAL IN WCHAR const *pwszfnOut,
    OPTIONAL IN WCHAR const *pwszPasswordArg)
{
    HRESULT hr;
    HRESULT hr2;
    BYTE *pbHash = NULL;
    DWORD cbHash;
    BOOL fVerboseOld = g_fVerbose;
    BOOL fQuietOld = g_fQuiet;
    WCHAR *pwszPassword = NULL;
    DWORD cCert = 0;
    DWORD cCRL = 0;
    DWORD cCTL = 0;

    if (g_fVerbose)
    {
	g_fVerbose--;
    }
    else
    {
	g_fQuiet = TRUE;
    }
    hr2 = S_OK;

    if (NULL != pwszCertName)
    {
	hr = WszToMultiByteInteger(TRUE, pwszCertName, &cbHash, &pbHash);
	_PrintIfError2(hr, "WszToMultiByteInteger", hr);
    }

    if (NULL != pwszCertName ||
	MAXDWORD != iCertSave ||
	(MAXDWORD == iCRLSave && MAXDWORD == iCTLSave))
    {
	hr = EnumCertsInStore(
			hStore,
			Mode, 
			iCertSave,
			pwszCertName,
			cbHash,
			pbHash,
			pwszfnOut,
			pwszPasswordArg,
			&pwszPassword,
			&cCert);
	_PrintIfError(hr, "EnumCertsInStore");
	if (S_OK == hr2)
	{
	    hr2 = hr;
	}
    }

    if (NULL != pwszCertName ||
	MAXDWORD != iCRLSave ||
	(MAXDWORD == iCertSave && MAXDWORD == iCTLSave))
    {
	hr = EnumCRLsInStore(
			hStore,
			Mode,
			iCRLSave,
			pwszCertName,
			cbHash,
			pbHash,
			pwszfnOut,
			&cCRL);
	_PrintIfError(hr, "EnumCRLsInStore");
	if (S_OK == hr2)
	{
	    hr2 = hr;
	}
    }

    if (NULL != pwszCertName ||
	MAXDWORD != iCTLSave ||
	(MAXDWORD == iCertSave && MAXDWORD == iCRLSave))
    {
	hr = EnumCTLsInStore(
			hStore,
			Mode,
			iCTLSave,
			pwszCertName,
			cbHash,
			pbHash,
			pwszfnOut,
			&cCTL);
	_PrintIfError(hr, "EnumCTLsInStore");
	if (S_OK == hr2)
	{
	    hr2 = hr;
	}
    }

    hr = hr2;
    if (S_OK == hr && NULL != pwszCertName && 0 == (cCert + cCRL + cCTL))
    {
	hr = NTE_NOT_FOUND;
        _JumpError(hr, error, "cuDumpAndVerifyStore");
    }

error:
    g_fVerbose = fVerboseOld;
    g_fQuiet = fQuietOld;
    if (NULL != pbHash)
    {
	LocalFree(pbHash);
    }
    if (NULL != pwszPassword)
    {
	LocalFree(pwszPassword);
    }
    return(hr);
}


#define wszLDAPPREFIX	L"ldap:///"

HRESULT
cuOpenCertStore(
    IN WCHAR const *pwszStoreName,
    IN OUT DWORD *pMode,
    OUT HCERTSTORE *phStore)
{
    HRESULT hr;
    WCHAR awc[7];
    LPCSTR pszStoreProvider = CERT_STORE_PROV_SYSTEM_REGISTRY_W;
    WCHAR *pwszStoreAlloc = NULL;
    WCHAR *pwszStoreAlloc2 = NULL;

    if (NULL == pwszStoreName ||
	0 == wcscmp(L"*", pwszStoreName) ||
	0 == lstrcmpi(wszCA_CERTSTORE, pwszStoreName))
    {
        pwszStoreName = wszCA_CERTSTORE;
    	*pMode |= DVNS_CASTORE;
    }
    wcsncpy(awc, pwszStoreName, ARRAYSIZE(awc));
    awc[ARRAYSIZE(awc) - 1] = L'\0';
    if (0 == lstrcmpi(L"ldap:/", awc))
    {
	pszStoreProvider = CERT_STORE_PROV_LDAP_W;
	*pMode |= DVNS_DSSTORE;
    }
    else
    {
	awc[3] = L'\0';
	if (0 == lstrcmpi(L"CN=", awc))
	{
	    pszStoreProvider = CERT_STORE_PROV_LDAP_W;
	    pwszStoreAlloc = (WCHAR *) LocalAlloc(
		    LMEM_FIXED, 
		    (WSZARRAYSIZE(wszLDAPPREFIX) + wcslen(pwszStoreName) + 1) *
			sizeof(WCHAR));
	    if (NULL == pwszStoreAlloc)
	    {
		hr = E_OUTOFMEMORY;
		_JumpError(hr, error, "LocalAlloc");
	    }
	    wcscpy(pwszStoreAlloc, wszLDAPPREFIX);
	    wcscat(pwszStoreAlloc, pwszStoreName);
	    pwszStoreName = pwszStoreAlloc;
	    *pMode |= DVNS_DSSTORE;
	}
    }
    if (DVNS_DSSTORE & *pMode)
    {
	hr = myInternetUncanonicalizeURL(pwszStoreName, &pwszStoreAlloc2);
	_JumpIfError(hr, error, "myInternetUncanonicalizeURL");

	pwszStoreName = pwszStoreAlloc2;
    }

    *phStore = CertOpenStore(
		pszStoreProvider,
		X509_ASN_ENCODING,
		NULL,		// hProv
		CERT_STORE_NO_CRYPT_RELEASE_FLAG |
		    CERT_STORE_ENUM_ARCHIVED_FLAG |
		    (((DVNS_REPAIRKPI | DVNS_WRITESTORE) & *pMode)?
			0 : CERT_STORE_READONLY_FLAG) |
		    (g_fForce? 0 : CERT_STORE_OPEN_EXISTING_FLAG) |
		    cuGetSystemStoreFlags(),
		pwszStoreName);
    if (NULL == *phStore)
    {
        hr = myHLastError();
        _JumpErrorStr(hr, error, "CertOpenStore", pwszStoreName);
    }
    hr = S_OK;

error:
    if (NULL != pwszStoreAlloc)
    {
	LocalFree(pwszStoreAlloc);
    }
    if (NULL != pwszStoreAlloc2)
    {
	LocalFree(pwszStoreAlloc2);
    }
    return(hr);
}


HRESULT
DumpAndVerifyNamedStore(
    IN WCHAR const *pwszStoreName,
    IN DWORD Mode,
    OPTIONAL IN WCHAR const *pwszCertName,
    IN DWORD iCertSave,
    IN DWORD iCRLSave,
    IN DWORD iCTLSave,
    OPTIONAL IN WCHAR const *pwszfnOut,
    OPTIONAL IN WCHAR const *pwszPassword)
{
    HRESULT hr;
    HCERTSTORE hStore = NULL;

    hr = cuOpenCertStore(pwszStoreName, &Mode, &hStore);
    _JumpIfError(hr, error, "cuOpenCertStore");

    hr = cuDumpAndVerifyStore(
			hStore,
			Mode,
			pwszCertName,
			iCertSave,
			iCRLSave,
			iCTLSave,
			pwszfnOut,
			pwszPassword);
    _JumpIfError(hr, error, "cuDumpAndVerifyStore");

error:
    if (NULL != hStore)
    {
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    return(hr);
}


HRESULT
verbViewOrDeleteStore(
    IN WCHAR const *pwszOption,
    OPTIONAL IN WCHAR const *pwszStoreName,
    OPTIONAL IN WCHAR const *pwszCertIndex,
    OPTIONAL IN WCHAR const *pwszfnOut,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR *pwszCertName = NULL;
    DWORD Mode;
    DWORD iCert;
    DWORD iCRL;
    DWORD iCTL;
    HCERTSTORE hStore = NULL;
    CERT_CONTEXT const *pCert = NULL;
    BOOL fDelete = g_wszViewDelStore == pwszOption;
    WCHAR *pwszSubject = NULL;

    hr = ParseCertCRLIndex(pwszCertIndex, &pwszCertName, &iCert, &iCRL, &iCTL);
    _JumpIfErrorStr(hr, error, "ParseCertCRLIndex", pwszCertIndex);

    Mode = DVNS_SAVECERT;
    if (fDelete)
    {
	Mode |= DVNS_WRITESTORE;
    }

    hr = cuOpenCertStore(pwszStoreName, &Mode, &hStore);
    _JumpIfError(hr, error, "cuOpenCertStore");

    hr = myGetCertificateFromPicker(
		g_hInstance,		// hInstance
		NULL,			// hwndParent
		IDS_VIEWSTORE_TITLE,	// idTitle
		fDelete? IDS_VIEWSTORE_SUBTITLE_DELETE :
			 IDS_VIEWSTORE_SUBTITLE, // idSubTitle
		0,			// dwFlags -- CUCS_*
		pwszCertName,		// pwszCommonName
		1,			// cStore
		&hStore,		// rghStore
		0,			// cpszObjId
		NULL,			// apszObjId
		&pCert);		// ppCert
    _JumpIfError(hr, error, "myGetCertificateFromPicker");

    if (NULL != pCert)
    {
	hr = myCertNameToStr(
		    X509_ASN_ENCODING,
		    &pCert->pCertInfo->Subject,
		    CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG,
		    &pwszSubject);
	_JumpIfError(hr, error, "myCertNameToStr");

	if (NULL != pwszfnOut)
	{
	    hr = EncodeToFileW(
			pwszfnOut,
			pCert->pbCertEncoded,
			pCert->cbCertEncoded,
			CRYPT_STRING_BINARY | g_EncodeFlags);
	    _JumpIfError(hr, error, "EncodeToFileW");
	    wprintf(
		myLoadResourceString(
		    IDS_FORMAT_SAVED_CERT_NAME), // "Saved certificate %ws"
		    pwszSubject);
	    wprintf(L": %ws\n", pwszfnOut);
	}
	if (fDelete)
	{
	    if (!CertDeleteCertificateFromStore(pCert))
	    {
		hr = myHLastError();
		_JumpError(hr, error, "CertDeleteCertificateFromStore");
	    }
	    pCert = NULL;

	    wprintf(
		myLoadResourceString(
		    IDS_FORMAT_DELETED_CERT_NAME), // "Deleted certificate %ws"
		    pwszSubject);
	    wprintf(wszNewLine);
	}
    }
    hr = S_OK;

error:
    if (NULL != pwszSubject)
    {
	LocalFree(pwszSubject);
    }
    if (NULL != pwszCertName)
    {
	LocalFree(pwszCertName);
    }
    if (NULL != pCert)
    {
	CertFreeCertificateContext(pCert);
    }
    if (NULL != hStore)
    {
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    return(hr);
}


HRESULT
verbStore(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszStoreName,
    IN WCHAR const *pwszCertIndex,
    IN WCHAR const *pwszfnOut,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR *pwszCertName = NULL;
    DWORD iCert;
    DWORD iCRL;
    DWORD iCTL;

    hr = ParseCertCRLIndex(pwszCertIndex, &pwszCertName, &iCert, &iCRL, &iCTL);
    _JumpIfErrorStr(hr, error, "ParseCertCRLIndex", pwszCertIndex);

    hr = DumpAndVerifyNamedStore(
		    pwszStoreName,
		    DVNS_SAVECERT |
			DVNS_SAVECRL |
			DVNS_SAVECTL |
			DVNS_DUMP |
			DVNS_DUMPKEYS |
			DVNS_DUMPPROPERTIES,
		    pwszCertName,
		    iCert,
		    iCRL,
		    iCTL,
		    pwszfnOut,
		    NULL);
    _JumpIfError(hr, error, "DumpAndVerifyNamedStore");

error:
    if (NULL != pwszCertName)
    {
	LocalFree(pwszCertName);
    }
    return(hr);
}


HRESULT
verbAddStore(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszStoreName,
    IN WCHAR const *pwszfnIn,
    IN WCHAR const *pwszArg3,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR *pwszStore = NULL;
    DWORD Mode;
    HCERTSTORE hStore = NULL;
    CERT_CONTEXT const *pCertContext = NULL;
    CRL_CONTEXT const *pCRLContext = NULL;
    BOOL fRoot = FALSE;

    Mode = DVNS_WRITESTORE;	// force open for write

    hr = cuOpenCertStore(pwszStoreName, &Mode, &hStore);
    if (S_OK != hr)
    {
	wprintf(myLoadResourceString(
		    g_fForce?
			IDS_CANNOT_CREATE_STORE :   // "Cannot open Cert store."
			IDS_CANNOT_OPEN_STORE));   // "Cannot open existing Cert store.  Use -f switch to force Cert store creation."
	wprintf(wszNewLine);
        _JumpErrorStr(hr, error, "cuOpenCertStore", pwszStoreName);
    }

    // Load and decode certificate

    hr = cuLoadCert(pwszfnIn, &pCertContext);
    if (S_OK != hr)
    {
	if (CRYPT_E_ASN1_BADTAG != hr)
	{
	    _JumpError(hr, error, "cuLoadCert");
	}
	hr = cuLoadCRL(pwszfnIn, &pCRLContext);
	_JumpIfError(hr, error, "cuLoadCRL");

	if (!CertAddCRLContextToStore(
				hStore,
				pCRLContext,
				CERT_STORE_ADD_REPLACE_EXISTING,
				NULL))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CertAddCRLContextToStore");
	}
    }
    else
    {
	if (CertCompareCertificateName(
			X509_ASN_ENCODING,
			&pCertContext->pCertInfo->Issuer,
			&pCertContext->pCertInfo->Subject))
	{
	    hr = cuVerifySignature(
			    pCertContext->pbCertEncoded,
			    pCertContext->cbCertEncoded,
			    &pCertContext->pCertInfo->SubjectPublicKeyInfo,
			    FALSE);
	    fRoot = S_OK == hr;
	    _PrintIfError(hr, "cuVerifySignature");
	}
	if (0 == lstrcmpi(pwszStoreName, L"root") && !fRoot)
	{
	    wprintf(myLoadResourceString(IDS_ROOT_STORE_NEEDS_ROOT_CERT));  // "Cannot add a non-root certificate to the root store"
	    wprintf(wszNewLine);
	    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "Non-root cert");
	}

	if (!CertAddCertificateContextToStore(
					hStore,
					pCertContext,
					CERT_STORE_ADD_REPLACE_EXISTING,
					NULL))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CertAddCertificateContextToStore");
	}
    }
    hr = S_OK;

error:
    cuUnloadCert(&pCertContext);
    cuUnloadCRL(&pCRLContext);
    if (NULL != pwszStore)
    {
	LocalFree(pwszStore);
    }
    if (NULL != hStore)
    {
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    return(hr);
}


HRESULT
verbDelStore(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszStoreName,
    IN WCHAR const *pwszCertIndex,
    IN WCHAR const *pwszArg3,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR *pwszStore = NULL;
    HCERTSTORE hStore = NULL;
    CERT_CONTEXT const *pCert = NULL;
    CRL_CONTEXT const *pCRL = NULL;
    WCHAR *pwszCertName = NULL;
    BYTE *pbHash = NULL;
    DWORD cbHash;
    BSTR strSerialNumber = NULL;
    DWORD Mode;
    DWORD iCert;
    DWORD iCertDel;
    DWORD iCRL;
    DWORD iCRLDel;
    DWORD iCTL;
    DWORD iCTLDel;

    if (NULL == pwszStoreName || 0 == wcscmp(L"*", pwszStoreName))
    {
        pwszStoreName = wszCA_CERTSTORE;
    }

    hr = ParseCertCRLIndex(pwszCertIndex, &pwszCertName, &iCertDel, &iCRLDel, &iCTLDel);
    _JumpIfErrorStr(hr, error, "ParseCertCRLIndex", pwszCertIndex);

    if (MAXDWORD == iCertDel && NULL == pwszCertName && MAXDWORD == iCRLDel)
    {
	hr = E_INVALIDARG;
	_JumpErrorStr(hr, error, "incomplete Index arg", pwszCertIndex);
    }
    if (NULL != pwszCertName)
    {
	hr = WszToMultiByteInteger(TRUE, pwszCertName, &cbHash, &pbHash);
	_PrintIfError2(hr, "WszToMultiByteInteger", hr);

	hr = myMakeSerialBstr(pwszCertName, &strSerialNumber);
	_PrintIfError2(hr, "myMakeSerialBstr", hr);
    }

    Mode = DVNS_WRITESTORE;	// force open for write

    hr = cuOpenCertStore(pwszStoreName, &Mode, &hStore);
    _JumpIfError(hr, error, "cuOpenCertStore");

    if (MAXDWORD != iCertDel || NULL != pwszCertName)
    {
	for (iCert = 0; ; iCert++)
	{
	    pCert = CertEnumCertificatesInStore(hStore, pCert);
	    if (NULL == pCert)
	    {
		break;
	    }
	    if (iCert == iCertDel ||
		(MAXDWORD == iCertDel && NULL != pwszCertName))
	    {
		CERT_CONTEXT const *pCertT;

		if (NULL != pwszCertName)
		{
		    BOOL fMatch;
		    
		    hr = myCertMatch(
				pCert,
				pwszCertName,
				FALSE,		// fAllowMissingCN
				pbHash,
				cbHash,
				strSerialNumber,
				&fMatch);
		    _JumpIfError(hr, error, "myCertMatch");

		    if (!fMatch)
		    {
			continue;
		    }
		}
		wprintf(
		    myLoadResourceString(IDS_FORMAT_DELETE_CERT_INDEX),  // "Deleting Certificate %d"
		    iCert);
		wprintf(wszNewLine);

		pCertT = CertDuplicateCertificateContext(pCert);
		if (!CertDeleteCertificateFromStore(pCertT))
		{
		    hr = myHLastError();
		    _JumpError(hr, error, "CertDeleteCertificateFromStore");
		}
		if (iCert == iCertDel)
		{
		    break;
		}
	    }
	}
    }
    if (MAXDWORD != iCRLDel)
    {
	for (iCRL = 0; ; iCRL++)
	{
	    pCRL = CertEnumCRLsInStore(hStore, pCRL);
	    if (NULL == pCRL)
	    {
		break;
	    }
	    if (iCRL == iCRLDel ||
		(MAXDWORD == iCRLDel && NULL != pwszCertName))
	    {
		CRL_CONTEXT const *pCRLT;

		if (NULL != pwszCertName)
		{
		    BOOL fMatch;
		    
		    hr = myCRLMatch(
				pCRL,
				pwszCertName,
				FALSE,		// fAllowMissingCN
				pbHash,
				cbHash,
				&fMatch);
		    _JumpIfError(hr, error, "myCRLMatch");

		    if (!fMatch)
		    {
			continue;
		    }
		}
		wprintf(
		    myLoadResourceString(IDS_FORMAT_DELETE_CRL_INDEX),  // "Deleting CRL %d"
		    iCRL);
		wprintf(wszNewLine);

		pCRLT = CertDuplicateCRLContext(pCRL);
		if (!CertDeleteCRLFromStore(pCRLT))
		{
		    hr = myHLastError();
		    _JumpError(hr, error, "CertDeleteCRLFromStore");
		}
		if (iCRL == iCRLDel)
		{
		    break;
		}
	    }
	}
    }
    hr = S_OK;

error:
    if (NULL != strSerialNumber)
    {
	SysFreeString(strSerialNumber);
    }
    if (NULL != pbHash)
    {
	LocalFree(pbHash);
    }
    if (NULL != pwszCertName)
    {
	LocalFree(pwszCertName);
    }
    if (NULL != pwszStore)
    {
	LocalFree(pwszStore);
    }
    if (NULL != pCert)
    {
	CertFreeCertificateContext(pCert);
    }
    if (NULL != pCRL)
    {
	CertFreeCRLContext(pCRL);
    }
    if (NULL != hStore)
    {
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    return(hr);
}


HRESULT
verbVerifyStore(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszStoreName,
    IN WCHAR const *pwszCertIndex,
    IN WCHAR const *pwszArg3,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR *pwszCertName = NULL;
    DWORD iCert;
    DWORD iCRL;
    DWORD iCTL;

    hr = ParseCertCRLIndex(pwszCertIndex, &pwszCertName, &iCert, &iCRL, &iCTL);
    _JumpIfErrorStr(hr, error, "ParseCertCRLIndex", pwszCertIndex);

    hr = DumpAndVerifyNamedStore(
		    pwszStoreName,
		    DVNS_SAVECERT |
			DVNS_SAVECRL |
			DVNS_SAVECTL |
			DVNS_VERIFYCERT |
			DVNS_DUMPKEYS |
			DVNS_DUMPPROPERTIES,
		    pwszCertName,
		    iCert,
		    iCRL,
		    iCTL,
		    NULL,
		    NULL);
    _JumpIfError(hr, error, "DumpAndVerifyNamedStore");

error:
    if (NULL != pwszCertName)
    {
	LocalFree(pwszCertName);
    }
    return(hr);
}


HRESULT
verbRepairStore(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszStoreName,
    IN WCHAR const *pwszCertIndex,
    IN WCHAR const *pwszArg3,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR *pwszCertName = NULL;
    DWORD iCert;
    DWORD iCRL;
    DWORD iCTL;

    hr = ParseCertCRLIndex(pwszCertIndex, &pwszCertName, &iCert, &iCRL, &iCTL);
    _JumpIfErrorStr(hr, error, "ParseCertCRLIndex", pwszCertIndex);

    hr = DumpAndVerifyNamedStore(
		    pwszStoreName,
		    DVNS_SAVECERT |
			DVNS_SAVECRL |
			DVNS_SAVECTL |
			DVNS_REPAIRKPI |
			DVNS_DUMPKEYS,
		    pwszCertName,
		    iCert,
		    iCRL,
		    iCTL,
		    NULL,
		    NULL);
    _JumpIfError(hr, error, "DumpAndVerifyNamedStore");

error:
    if (NULL != pwszCertName)
    {
	LocalFree(pwszCertName);
    }
    return(hr);
}


HRESULT
verbExportPVK(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszCertIndex,
    IN WCHAR const *pwszfnPVKBaseName,
    IN WCHAR const *pwszStoreName,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR *pwszCertName = NULL;
    DWORD iCert;
    DWORD iCRL;
    DWORD iCTL;

    hr = ParseCertCRLIndex(pwszCertIndex, &pwszCertName, &iCert, &iCRL, &iCTL);
    _JumpIfErrorStr(hr, error, "ParseCertCRLIndex", pwszCertIndex);

    hr = DumpAndVerifyNamedStore(
		    NULL == pwszStoreName? wszMY_CERTSTORE : pwszStoreName,
		    DVNS_SAVEPVK | DVNS_DUMPKEYS,
		    pwszCertName,
		    iCert,
		    iCRL,
		    iCTL,
		    pwszfnPVKBaseName,
		    NULL);
    _JumpIfError(hr, error, "DumpAndVerifyNamedStore");

error:
    if (NULL != pwszCertName)
    {
	LocalFree(pwszCertName);
    }
    return(hr);
}


HRESULT
verbExportPFX(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszCertIndex,
    IN WCHAR const *pwszfnPFX,
    IN WCHAR const *pwszPassword,
    IN WCHAR const *pwszStoreName)
{
    HRESULT hr;
    WCHAR *pwszCertName = NULL;
    DWORD iCert;
    DWORD iCRL;
    DWORD iCTL;

    hr = ParseCertCRLIndex(pwszCertIndex, &pwszCertName, &iCert, &iCRL, &iCTL);
    _JumpIfErrorStr(hr, error, "ParseCertCRLIndex", pwszCertIndex);

    hr = DumpAndVerifyNamedStore(
		    NULL == pwszStoreName? wszMY_CERTSTORE : pwszStoreName,
		    DVNS_SAVEPFX | DVNS_DUMPKEYS,
		    pwszCertName,
		    iCert,
		    iCRL,
		    iCTL,
		    pwszfnPFX,
		    pwszPassword);
    _JumpIfError(hr, error, "DumpAndVerifyNamedStore");

error:
    if (NULL != pwszCertName)
    {
	LocalFree(pwszCertName);
    }
    return(hr);
}


HRESULT
cuImportChainAndKeys(
    IN CERT_CHAIN_CONTEXT const *pChain,
    IN BOOL fUser,
    OPTIONAL IN WCHAR const *pwszStoreName)
{
    HRESULT hr;
    CRYPT_KEY_PROV_INFO *pkpi = NULL;
    WCHAR *pwszSimpleName = NULL;
    WCHAR *pwszRawContainerName = NULL;
    WCHAR *pwszKeyContainerName = NULL;
    CERT_CONTEXT const *pcc;
    DWORD cwc;
    WCHAR *pwsz;

    if (NULL == pwszStoreName)
    {
	pwszStoreName = wszMY_CERTSTORE;
    }
    pcc = pChain->rgpChain[0]->rgpElement[0]->pCertContext;

    hr = myCertGetNameString(
			pcc,
			CERT_NAME_SIMPLE_DISPLAY_TYPE,
			&pwszSimpleName);
    _JumpIfError(hr, error, "myCertGetNameString");

    hr = myCertGetKeyProviderInfo(pcc, &pkpi);
    _JumpIfError(hr, error, "myCertGetKeyProviderInfo");

    for (pwsz = pkpi->pwszContainerName; L'\0' != *pwsz; pwsz++)
    {
	if (L'A' <= *pwsz && L'F' >= *pwsz)
	{
	    *pwsz += L'a' - L'A';
	}
    }
    pwsz = pkpi->pwszContainerName;
    if (wcLBRACE == *pwsz)
    {
	pwsz++;
    }

    cwc = wcslen(pwszSimpleName) + 1 + wcslen(pwsz);
    pwszRawContainerName = (WCHAR *) LocalAlloc(
					LMEM_FIXED,
					(cwc + 1) * sizeof(WCHAR));
    if (NULL == pwszRawContainerName)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    wcscpy(pwszRawContainerName, pwszSimpleName);
    wcscat(pwszRawContainerName, L"-");
    wcscat(pwszRawContainerName, pwsz);
    pwsz = &pwszRawContainerName[wcslen(pwszRawContainerName) - 1];
    if (wcLBRACE == *pkpi->pwszContainerName && wcRBRACE == *pwsz)
    {
	*pwsz = L'\0';
    }

    hr = mySanitizeName(pwszRawContainerName, &pwszKeyContainerName);
    _JumpIfError(hr, error, "mySanitizeName");

    wprintf(L"%ws -- %ws\n", pwszSimpleName, pwszKeyContainerName);

    hr = myCopyKeys(
		pkpi,
		pkpi->pwszContainerName,	// pwszOldContainer
		pwszKeyContainerName,		// pwszNewContainer
		fUser,				// fOldUserKey
		fUser,				// fNewUserKey
		g_fForce);
    _JumpIfError(hr, error, "myCopyKeys");

    pkpi->pwszContainerName = pwszKeyContainerName;

    hr = mySaveChainAndKeys(
			pChain->rgpChain[0],
			pwszStoreName,
			cuGetSystemStoreFlags(),
			pkpi,
			NULL);
    _JumpIfError(hr, error, "mySaveChainAndKeys");

error:
    if (NULL != pkpi)
    {
	LocalFree(pkpi);
    }
    if (NULL != pwszSimpleName)
    {
        LocalFree(pwszSimpleName);
    }
    if (NULL != pwszRawContainerName)
    {
        LocalFree(pwszRawContainerName);
    }
    if (NULL != pwszKeyContainerName)
    {
        LocalFree(pwszKeyContainerName);
    }
    return(hr);
}


HRESULT
verbImportPFX(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszfnPFX,
    IN WCHAR const *pwszPassword,
    IN WCHAR const *pwszStoreName,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    CRYPT_DATA_BLOB pfx;
    WCHAR wszPassword[MAX_PATH];
    HCERTSTORE hStorePFX = NULL;
    RESTORECHAIN *paRestoreChain = NULL;
    DWORD cRestoreChain;
    DWORD iChain;
    BOOL fUser = !g_fEnterpriseRegistry && g_fUserRegistry;

    pfx.pbData = NULL;

    if (NULL == pwszPassword || 0 == wcscmp(L"*", pwszPassword))
    {
	hr = cuGetPassword(FALSE, wszPassword, ARRAYSIZE(wszPassword));
	_JumpIfError(hr, error, "cuGetPassword");

	pwszPassword = wszPassword;
    }
    hr = DecodeFileW(pwszfnPFX, &pfx.pbData, &pfx.cbData, CRYPT_STRING_ANY);
    _JumpIfError(hr, error, "DecodeFileW");

    CSASSERT(NULL != pfx.pbData);

    if (!PFXIsPFXBlob(&pfx))
    {
        hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
        _JumpError(hr, error, "PFXIsPFXBlob");
    }

    hStorePFX = myPFXImportCertStore(
				&pfx,
				pwszPassword,
				CRYPT_EXPORTABLE | 
				    (fUser? 0 : CRYPT_MACHINE_KEYSET));
    if (NULL == hStorePFX)
    {
        hr = myHLastError();
        _JumpError(hr, error, "myPFXImportCertStore");
    }

    cRestoreChain = 0;
    hr = myGetChainArrayFromStore(
                                hStorePFX,
				FALSE,
				fUser,
                                NULL,		// ppwszCommonName
                                &cRestoreChain,
                                NULL);
    _JumpIfError(hr, error, "myGetChainArrayFromStore");

    if (0 == cRestoreChain)
    {
        hr = HRESULT_FROM_WIN32(CRYPT_E_SELF_SIGNED);
        _JumpError(hr, error, "myGetChainArrayFromStore <no chain>");
    }

    paRestoreChain = (RESTORECHAIN *) LocalAlloc(
				    LMEM_FIXED | LMEM_ZEROINIT,
				    cRestoreChain * sizeof(paRestoreChain[0]));
    if (NULL == paRestoreChain)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    hr = myGetChainArrayFromStore(
			    hStorePFX,
			    FALSE,
			    fUser,
			    NULL,		// ppwszCommonName
			    &cRestoreChain,
			    paRestoreChain);
    _JumpIfError(hr, error, "myGetChainArrayFromStore");

    for (iChain = 0; iChain < cRestoreChain; iChain++)
    {
	CERT_CHAIN_CONTEXT const *pChain = paRestoreChain[iChain].pChain;
	CERT_PUBLIC_KEY_INFO *pPublicKeyInfo;

	if (1 > pChain->cChain)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "No Chain Context");
	}
	hr = cuImportChainAndKeys(pChain, fUser, pwszStoreName);
	_JumpIfError(hr, error, "cuImportChainAndKeys");
    }
    hr = S_OK;

error:
    if (NULL != paRestoreChain)
    {
        for (iChain = 0; iChain < cRestoreChain; iChain++)
	{
	    if (NULL != paRestoreChain[iChain].pChain)
	    {
		CertFreeCertificateChain(paRestoreChain[iChain].pChain);
	    }
	}
	LocalFree(paRestoreChain);
    }
    if (NULL != hStorePFX)
    {
        myDeleteGuidKeys(hStorePFX, !fUser);
	CertCloseStore(hStorePFX, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    if (NULL != pfx.pbData)
    {
        LocalFree(pfx.pbData);
    }
    return(hr);
}


HRESULT
AddStringToList(
    IN WCHAR const *pwszNew,
    IN OUT WCHAR ***papwsz)
{
    HRESULT hr;
    WCHAR *pwszAlloc = NULL;
    WCHAR **ppwsz;
    DWORD i;

    // Count the strings in the existing list.
    // If the new string matches an existing string, return imemdiately.

    ppwsz = *papwsz;
    i = 0;
    if (NULL != ppwsz)
    {
	for ( ; NULL != ppwsz[i]; i++)
	{
	    if (0 == lstrcmp(pwszNew, ppwsz[i]))
	    {
		hr = S_OK;
		goto error;
	    }
	}
    }
    hr = myDupString(pwszNew, &pwszAlloc);
    _JumpIfError(hr, error, "myDupString");

    ppwsz = (WCHAR **) LocalAlloc(LMEM_FIXED, (i + 2) * sizeof(*ppwsz));
    if (NULL == ppwsz)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    // Insert the new string at the head of the list.

    ppwsz[0] = pwszAlloc;
    pwszAlloc = NULL;
    if (0 != i)
    {
	CopyMemory(&ppwsz[1], *papwsz, i * sizeof(*ppwsz));
	LocalFree(*papwsz);
    }
    ppwsz[i + 1] = NULL;
    *papwsz = ppwsz;
    hr = S_OK;

error:
    if (NULL != pwszAlloc)
    {
	LocalFree(pwszAlloc);
    }
    return(hr);
}


HRESULT
AddPFXToStore(
    IN WCHAR const *pwszfn,
    IN HCERTSTORE hStoreMerge,
    IN OUT WCHAR ***papwszPasswordList)
{
    HRESULT hr;
    WCHAR const * const *ppwszPasswordList = *papwszPasswordList;
    DWORD i;
    CRYPT_DATA_BLOB pfx;
    HCERTSTORE hStorePFX = NULL;
    WCHAR wszPassword[MAX_PATH];
    WCHAR const *pwszPassword;
    CERT_CONTEXT const *pCert = NULL;

    pfx.pbData = NULL;

    hr = DecodeFileW(pwszfn, &pfx.pbData, &pfx.cbData, CRYPT_STRING_ANY);
    _JumpIfError(hr, error, "DecodeFileW");

    if (!PFXIsPFXBlob(&pfx))
    {
        hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
        _JumpError(hr, error, "PFXIsPFXBlob");
    }

    // Try all of the passwords collected so far.

    if (NULL != ppwszPasswordList)
    {
	for (i = 0; NULL != ppwszPasswordList[i]; i++)
	{
	    hStorePFX = myPFXImportCertStore(
					&pfx,
					ppwszPasswordList[i],
					CRYPT_EXPORTABLE);
	    if (NULL != hStorePFX)
	    {
		break;
	    }
	    hr = myHLastError();
	    _PrintError2(
		    hr,
		    "myPFXImportCertStore",
		    HRESULT_FROM_WIN32(ERROR_INVALID_PASSWORD));
	}
    }

    // Try the unparsed command line password, or collect a new one.

    pwszPassword = g_pwszPassword;
    while (NULL == hStorePFX)
    {
	if (NULL != pwszPassword)
	{
	    hStorePFX = myPFXImportCertStore(
					&pfx,
					pwszPassword,
					CRYPT_EXPORTABLE);
	    if (NULL != hStorePFX)
	    {
		break;
	    }
	    hr = myHLastError();
	    _PrintError2(
		    hr,
		    "myPFXImportCertStore",
		    HRESULT_FROM_WIN32(ERROR_INVALID_PASSWORD));
	}

	wprintf(L"Enter password for %ws:\n", pwszfn);
	hr = cuGetPassword(FALSE, wszPassword, ARRAYSIZE(wszPassword));
	_JumpIfError(hr, error, "cuGetPassword");

	hr = AddStringToList(wszPassword, papwszPasswordList);
	_JumpIfError(hr, error, "AddStringToList");

	pwszPassword = wszPassword;
    }
    CSASSERT(NULL != hStorePFX);
    while (TRUE)
    {
	pCert = CertEnumCertificatesInStore(hStorePFX, pCert);
	if (NULL == pCert)
	{
	    break;
	}
	if (!CertAddCertificateContextToStore(
					hStoreMerge,
					pCert,
					CERT_STORE_ADD_REPLACE_EXISTING,
					NULL))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CertAddCertificateContextToStore");
	}
	if (!CertDeleteCertificateFromStore(pCert))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CertDeleteCertificateFromStore");
	}
	pCert = NULL;
    }
    hr = S_OK;

error:
    if (NULL != pCert)
    {
	CertFreeCertificateContext(pCert);
    }
    if (NULL != hStorePFX)
    {
        myDeleteGuidKeys(hStorePFX, FALSE);
	CertCloseStore(hStorePFX, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    if (NULL != pfx.pbData)
    {
        LocalFree(pfx.pbData); 
    }
    return(hr);
}


HRESULT
verbMergePFX(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszfnPFXInFileList,
    IN WCHAR const *pwszfnPFXOutFile,
    IN WCHAR const *pwszArg3,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR **ppwszfnList = NULL;
    WCHAR **ppwszPasswordList = NULL;
    DWORD i;
    HCERTSTORE hStoreMerge = NULL;
    WCHAR *pwszPasswordAlloc = NULL;
    WCHAR *pwszPasswordOut;

    hr = cuParseStrings(
		pwszfnPFXInFileList,
		FALSE,
		NULL,
		NULL,
		&ppwszfnList,
		NULL);
    _JumpIfError(hr, error, "cuParseStrings");

    pwszPasswordOut = NULL;
    if (NULL != g_pwszPassword)
    {
	hr = cuParseStrings(
		    g_pwszPassword,
		    FALSE,
		    NULL,
		    NULL,
		    &ppwszPasswordList,
		    NULL);
	_JumpIfError(hr, error, "cuParseStrings");

	for (i = 0; NULL != ppwszPasswordList[i]; i++)
	{
	}
	if (i > 1 && 0 != lstrcmp(L"*", ppwszPasswordList[i - 1]))
	{
	    pwszPasswordOut = ppwszPasswordList[i - 1];
	}
    }
    hStoreMerge = CertOpenStore(
			    CERT_STORE_PROV_MEMORY,
			    X509_ASN_ENCODING,
			    NULL,
			    CERT_STORE_NO_CRYPT_RELEASE_FLAG |
				CERT_STORE_ENUM_ARCHIVED_FLAG,
			    NULL);
    if (NULL == hStoreMerge)
    {
        hr = myHLastError();
        _JumpError(hr, error, "CertOpenStore");
    }
    for (i = 0; NULL != ppwszfnList[i]; i++)
    {
	hr = AddPFXToStore(
		    ppwszfnList[i],
		    hStoreMerge,
		    &ppwszPasswordList);
	_JumpIfError(hr, error, "AddPFXToStore");
    }
    hr = SavePFXStoreToFile(
		    hStoreMerge,
		    pwszfnPFXOutFile,
		    pwszPasswordOut,
		    &pwszPasswordAlloc);
    _JumpIfError(hr, error, "SavePFXStoreToFile");

error:
    if (NULL != hStoreMerge)
    {
        myDeleteGuidKeys(hStoreMerge, FALSE);
	CertCloseStore(hStoreMerge, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    cuFreeStringArray(ppwszPasswordList);
    cuFreeStringArray(ppwszfnList);
    if (NULL != pwszPasswordAlloc)
    {
	LocalFree(pwszPasswordAlloc);
    }
    return(hr);
}


HRESULT
GetMarshaledDword(
    IN BOOL fFetchLength,
    IN OUT BYTE const **ppb,
    IN OUT DWORD *pcb,
    OUT DWORD *pdw)
{
    HRESULT hr = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);

    if (sizeof(*pdw) > *pcb)
    {
	_JumpError(hr, error, "input buffer too small");
    }
    *pdw = *(DWORD UNALIGNED *) *ppb;
    *ppb += sizeof(*pdw);
    *pcb -= sizeof(*pdw);
    if (fFetchLength && *pdw > *pcb)
    {
	_JumpError(hr, error, "input buffer too small for length");
    }
    hr = S_OK;

error:
    return(hr);
}


HRESULT
DecodeSequence(
    IN BYTE const *pbSeq,
    IN DWORD cbSeq,
    IN DWORD cSeq,
    OUT CRYPT_SEQUENCE_OF_ANY **ppSeq)
{
    HRESULT hr;
    DWORD cb;
    DWORD i;
    CRYPT_SEQUENCE_OF_ANY *pSeq = NULL;

    *ppSeq = NULL;
    if (!myDecodeObject(
		    X509_ASN_ENCODING,
		    X509_SEQUENCE_OF_ANY,
		    pbSeq,
		    cbSeq,
		    CERTLIB_USE_LOCALALLOC,
		    (VOID **) &pSeq,
		    &cb))
    {
	hr = myHLastError();
	_JumpError(hr, error, "myDecodeObject");
    }
    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
    if (cSeq != pSeq->cValue)
    {
	_JumpError(hr, error, "Sequence count");
    }
    for (i = 0; i < cSeq; i++)
    {
	if (NULL == pSeq->rgValue[i].pbData || 0 == pSeq->rgValue[i].cbData)
	{
	    _JumpError(hr, error, "Empty Sequence");
	}
    }
    *ppSeq = pSeq;
    pSeq = NULL;
    hr = S_OK;

error:
    if (NULL != pSeq)
    {
	LocalFree(pSeq);
    }
    return(hr);
}


#define k_PrivateKeyVersion	0

HRESULT
VerifyKeyVersion(
    IN BYTE const *pbIn,
    IN DWORD cbIn)
{
    HRESULT hr;
    DWORD dwKeyVersion;
    DWORD cb;

    dwKeyVersion = MAXDWORD;
    cb = sizeof(dwKeyVersion);
    if (!CryptDecodeObject(
		    X509_ASN_ENCODING,
		    X509_INTEGER,
		    pbIn,
		    cbIn,
		    0,
		    &dwKeyVersion,
		    &cb))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptDecodeObject");
    }
    if (k_PrivateKeyVersion != dwKeyVersion)
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	_JumpError(hr, error, "Public key version");
    }
    hr = S_OK;

error:
    return(hr);
}


//+-------------------------------------------------------------------------
// Inputs a private key in PKCS PrivateKeyInfo format:
//  RSAPrivateKeyInfo ::= SEQUENCE {
//      version             Version,    -- only 0 supported
//      privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
//      privateKey          PrivateKey
//  }
//
//  Version ::= INTEGER
//
//  PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
//
//  PrivateKey ::= OCTET STRING     -- contains an RSAPrivateKey
//
//  RSAPrivateKey ::= SEQUENCE {
//      version         Version,    -- only 0 supported
//      modulus         INTEGER,    -- n
//      publicExponent  INTEGER,    -- e
//      privateExponent INTEGER,    -- d
//      prime1          INTEGER,    -- p
//      prime2          INTEGER,    -- q
//      exponent1       INTEGER,    -- d mod (p-1)
//      exponent2       INTEGER,    -- d mod (q-1)
//      coefficient     INTEGER     -- (inverse of q) mod p
//  }
//
// returns a PRIVATEKEYBLOB
//--------------------------------------------------------------------------

// Indexes into pSeqOuter:
#define ISO_VERSION	0
#define ISO_ALG		1
#define ISO_KEY		2
#define ISO_MAX		3	// number of elements

// Indexes into pSeqAlg:
#define ISA_OID		0
#define ISA_PARM	1
#define ISA_MAX		2	// number of elements

// Indexes into pSeqKey:
#define ISK_VERSION	0
#define ISK_MODULUS	1	// public key
#define ISK_PUBEXP	2
#define ISK_PRIVEXP	3
#define ISK_PRIME1	4
#define ISK_PRIME2	5
#define ISK_EXP1	6
#define ISK_EXP2	7
#define ISK_COEFF	8
#define ISK_MAX		9	// number of elements

typedef struct _KEYBLOBMAP
{
    DWORD dwisk;	// index into pSeqKey: ISK_*
    DWORD dwdivisor;	// cbitKey/dwDivisor is expected byte count
} KEYBLOBMAP;

// The KEYBLOBMAP array defines the order and expected size of the key element
// integers as they will appear in the RSA PRIVATEKEYBLOB.

KEYBLOBMAP g_akbm[] = {
    { ISK_MODULUS, 8 },		// public key
    { ISK_PRIME1,  16 },
    { ISK_PRIME2,  16 },
    { ISK_EXP1,    16 },
    { ISK_EXP2,    16 },
    { ISK_COEFF,   16 },
    { ISK_PRIVEXP, 8 },
};

HRESULT
myDecodeKMSRSAKey(
    IN BYTE const *pbKMSRSAKey,
    IN DWORD cbKMSRSAKey,
    OUT BYTE **ppbKey,
    OUT DWORD *pcbKey)
{
    HRESULT hr;
    CRYPT_SEQUENCE_OF_ANY *pSeqOuter = NULL;
    CRYPT_SEQUENCE_OF_ANY *pSeqAlg = NULL;
    CRYPT_SEQUENCE_OF_ANY *pSeqKey = NULL;
    DWORD cb;
    DWORD i;
    BYTE *pb;
    BYTE *pbKey = NULL;
    DWORD cbKey;
    DWORD cbitKey;
    char *pszObjId = NULL;
    CRYPT_DATA_BLOB *pBlobKey = NULL;
    CRYPT_INTEGER_BLOB *apIntKey[ISK_MAX];
    DWORD dwPubExp;

    *ppbKey = NULL;
    ZeroMemory(apIntKey, sizeof(apIntKey));
    hr = DecodeSequence(pbKMSRSAKey, cbKMSRSAKey, ISO_MAX, &pSeqOuter);
    _JumpIfError(hr, error, "DecodeSequence");

    hr = VerifyKeyVersion(
		    pSeqOuter->rgValue[ISO_VERSION].pbData,
		    pSeqOuter->rgValue[ISO_VERSION].cbData);
    _JumpIfError(hr, error, "VerifyKeyVersion");

    hr = DecodeSequence(
		pSeqOuter->rgValue[ISO_ALG].pbData,
		pSeqOuter->rgValue[ISO_ALG].cbData,
		ISA_MAX,
		&pSeqAlg);
    _JumpIfError(hr, error, "DecodeSequence");

    hr = cuDecodeObjId(
		pSeqAlg->rgValue[ISA_OID].pbData,
		pSeqAlg->rgValue[ISA_OID].cbData,
		&pszObjId);
    _JumpIfError(hr, error, "cuDecodeObjId");

    // key algorithm must be szOID_RSA_RSA

    if (0 != strcmp(szOID_RSA_RSA, pszObjId))
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	_JumpError(hr, error, "Bad key alg ObjId");
    }

    // key algorithm parms must be NULL (BER_NULL, cb == 0)

    if (2 != pSeqAlg->rgValue[ISA_PARM].cbData ||
	BER_NULL != pSeqAlg->rgValue[ISA_PARM].pbData[0] ||
	0 != pSeqAlg->rgValue[ISA_PARM].pbData[1])
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	_JumpError(hr, error, "Bad key alg parameters");
    }

    if (!myDecodeObject(
		    X509_ASN_ENCODING,
		    X509_OCTET_STRING,
		    pSeqOuter->rgValue[ISO_KEY].pbData,
		    pSeqOuter->rgValue[ISO_KEY].cbData,
		    CERTLIB_USE_LOCALALLOC,
		    (VOID **) &pBlobKey,
		    &cb))
    {
	hr = myHLastError();
	_JumpError(hr, error, "myDecodeObject");
    }

    hr = DecodeSequence(
		pBlobKey->pbData,
		pBlobKey->cbData,
		ARRAYSIZE(apIntKey),
		&pSeqKey);
    _JumpIfError(hr, error, "DecodeSequence");

    hr = VerifyKeyVersion(
		    pSeqKey->rgValue[ISK_VERSION].pbData,
		    pSeqKey->rgValue[ISK_VERSION].cbData);
    _JumpIfError(hr, error, "VerifyKeyVersion");

    cb = sizeof(dwPubExp);
    if (!CryptDecodeObject(
		    X509_ASN_ENCODING,
		    X509_INTEGER,
		    pSeqKey->rgValue[ISK_PUBEXP].pbData,
		    pSeqKey->rgValue[ISK_PUBEXP].cbData,
		    0,
		    &dwPubExp,
		    &cb))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptDecodeObject");
    }

    for (i = 0; i < ARRAYSIZE(apIntKey); i++)
    {
	if (!myDecodeObject(
			X509_ASN_ENCODING,
			X509_MULTI_BYTE_INTEGER,
			pSeqKey->rgValue[i].pbData,
			pSeqKey->rgValue[i].cbData,
			CERTLIB_USE_LOCALALLOC,
			(VOID **) &apIntKey[i],
			&cb))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "myDecodeObject");
	}
    }
    cbitKey = apIntKey[ISK_MODULUS]->cbData * 8;

#if 0
    for (i = 0; i < ARRAYSIZE(apIntKey); i++)
    {
	wprintf(wszNewLine);
	DumpHex(
	    DH_NOTABPREFIX | DH_NOASCIIHEX | 4,
	    apIntKey[i]->pbData,
	    apIntKey[i]->cbData);
    }
#endif
    cbKey = sizeof(BLOBHEADER) + sizeof(RSAPUBKEY);
    for (i = 0; i < ARRAYSIZE(g_akbm); i++)
    {
	cbKey += cbitKey / g_akbm[i].dwdivisor;
    }
    pbKey = (BYTE *) LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, cbKey);
    if (NULL == pbKey)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    pb = pbKey;
    ((BLOBHEADER *) pb)->bType = PRIVATEKEYBLOB;
    ((BLOBHEADER *) pb)->bVersion = CUR_BLOB_VERSION;
    ((BLOBHEADER *) pb)->aiKeyAlg = CALG_RSA_KEYX;

    pb += sizeof(BLOBHEADER);
    ((RSAPUBKEY *) pb)->magic = 0x32415352;	// "RSA2"
    ((RSAPUBKEY *) pb)->bitlen = cbitKey;
    ((RSAPUBKEY *) pb)->pubexp = dwPubExp;

    pb += sizeof(RSAPUBKEY);
    for (i = 0; i < ARRAYSIZE(g_akbm); i++)
    {
	CSASSERT(ISK_MAX > g_akbm[i].dwisk);
	cb = cbitKey / g_akbm[i].dwdivisor;
	if (cb != apIntKey[g_akbm[i].dwisk]->cbData)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "Bad key element size");
	}
	CopyMemory(pb, apIntKey[g_akbm[i].dwisk]->pbData, cb);
	pb += cb;
    }
    CSASSERT(pb = &pbKey[cbKey]);
    if (g_fVerbose)
    {
	wprintf(L"%d bit key\n", cbitKey);
	if (1 < g_fVerbose)
	{
	    DumpHex(DH_NOTABPREFIX | 4, pbKey, cbKey);
	}
    }
    *pcbKey = cbKey;
    *ppbKey = pbKey;
    pbKey = NULL;
    hr = S_OK;

error:
    if (NULL != pSeqOuter)
    {
	LocalFree(pSeqOuter);
    }
    if (NULL != pSeqAlg)
    {
	LocalFree(pSeqAlg);
    }
    if (NULL != pSeqKey)
    {
	LocalFree(pSeqKey);
    }
    if (NULL != pszObjId)
    {
	LocalFree(pszObjId);
    }
    if (NULL != pBlobKey)
    {
	LocalFree(pBlobKey);
    }
    for (i = 0; i < ARRAYSIZE(apIntKey); i++)
    {
	if (NULL != apIntKey[i])
	{
	    LocalFree(apIntKey[i]);
	}
    }
    if (NULL != pbKey)
    {
	LocalFree(pbKey);
    }
    return(hr);
}


HRESULT
myVerifyKMSKey(
    IN BYTE const *pbCert,
    IN DWORD cbCert,
    IN BYTE const *pbKey,
    IN DWORD cbKey)
{
    HRESULT hr;
    CERT_CONTEXT const *pCert;
    HCRYPTPROV hProv = NULL;
    HCRYPTKEY hKey = NULL;
    CERT_PUBLIC_KEY_INFO *pPublicKeyInfo = NULL;
    DWORD cb;

    pCert = CertCreateCertificateContext(X509_ASN_ENCODING, pbCert, cbCert);
    if (NULL == pCert)
    {
	hr = myHLastError();
	_JumpError(hr, error, "CertCreateCertificateContext");
    }

    if (!CryptAcquireContext(
			&hProv,
			NULL,
			NULL,
			PROV_RSA_FULL,
			CRYPT_VERIFYCONTEXT))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptAcquireContext");
    }
    if (!CryptImportKey(hProv, pbKey, cbKey, NULL, CRYPT_EXPORTABLE, &hKey))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptImportKey");
    }
    if (!myCryptExportPublicKeyInfo(
				hProv,
				AT_KEYEXCHANGE,
				CERTLIB_USE_LOCALALLOC,
				&pPublicKeyInfo,
				&cb))
    {
	hr = myHLastError();
	_JumpError(hr, error, "myCryptExportPublicKeyInfo");
    }
    if (g_fVerbose)
    {
	cuDumpVersion(pCert->pCertInfo->dwVersion + 1);
	if (1 < g_fVerbose)
	{
	    cuDumpPublicKey(&pCert->pCertInfo->SubjectPublicKeyInfo);
	    cuDumpPublicKey(pPublicKeyInfo);
	}
    }

    if (!myCertComparePublicKeyInfo(
			    X509_ASN_ENCODING,
			    CERT_V1 == pCert->pCertInfo->dwVersion,
			    pPublicKeyInfo,
			    &pCert->pCertInfo->SubjectPublicKeyInfo))
    {
	// by design, (my)CertComparePublicKeyInfo doesn't set last error!

	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	wprintf(myLoadResourceString(IDS_ERR_PUBLICKEY_MISMATCH)); // "ERROR: Certificate public key does NOT match stored keyset"
	wprintf(wszNewLine);
	_JumpError(hr, error, "myCertComparePublicKeyInfo");
    }
    hr = myValidateKeyForEncrypting(
			    hProv,
			    &pCert->pCertInfo->SubjectPublicKeyInfo,
			    CALG_RC4);
    if (S_OK != hr)
    {
	wprintf(myLoadResourceString(IDS_ERR_PRIVATEKEY_MISMATCH)); // "ERROR: Certificate public key does NOT match private key"
	wprintf(wszNewLine);
	_JumpError(hr, error, "myValidateKeyForEncrypting");
    }
    if (g_fVerbose)
    {
	wprintf(L"Private key verifies\n");
    }

error:
    if (NULL != pCert)
    {
	CertFreeCertificateContext(pCert);
    }
    if (NULL != pPublicKeyInfo)
    {
	LocalFree(pPublicKeyInfo);
    }
    if (NULL != hKey)
    {
	CryptDestroyKey(hKey);
    }
    if (NULL != hProv)
    {
	CryptReleaseContext(hProv, 0);
    }
    return(hr);
}


HRESULT
cuDumpAsnBinaryQuiet(
    IN BYTE const *pb,
    IN DWORD cb,
    IN DWORD iElement)
{
    HRESULT hr;
    BOOL fVerboseOld = g_fVerbose;
    BOOL fQuietOld = g_fQuiet;

    if (g_fVerbose)
    {
	g_fVerbose--;
    }
    else
    {
	g_fQuiet = TRUE;
    }
    hr = cuDumpAsnBinary(pb, cb, iElement);
    _JumpIfError(hr, error, "cuDumpAsnBinary");

error:
    g_fVerbose = fVerboseOld;
    g_fQuiet = fQuietOld;
    return(hr);
}


HRESULT
ReadTaggedBlob(
    IN HANDLE hFile,
    IN DWORD cbRemain,
    OUT TagHeader *pth,
    OUT BYTE **ppb)
{
    HRESULT hr;
    DWORD cbRead;
    
    *ppb = NULL;
    if (!ReadFile(hFile, pth, sizeof(*pth), &cbRead, NULL))
    {
	hr = myHLastError();
	_JumpError(hr, error, "ReadFile");
    }
    hr = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
    if (cbRead != sizeof(*pth))
    {
	DBGPRINT((
	    DBG_SS_ERROR,
	    "ReadFile read %u bytes, requested %u\n",
	    cbRead,
	    sizeof(*pth)));
	_JumpError(hr, error, "ReadFile(cbRead)");
    }
    if (cbRead + pth->cbSize > cbRemain)
    {
	DBGPRINT((
	    DBG_SS_ERROR,
	    "Header size %u bytes, cbRemain %u\n",
	    sizeof(*pth) + pth->cbSize,
	    cbRemain));
	_JumpError(hr, error, "cbRemain");
    }

    *ppb = (BYTE *) LocalAlloc(LMEM_FIXED, pth->cbSize);
    if (NULL == *ppb)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    if (!ReadFile(hFile, *ppb, pth->cbSize, &cbRead, NULL))
    {
	hr = myHLastError();
	_JumpError(hr, error, "ReadFile");
    }
    if (cbRead != pth->cbSize)
    {
	hr = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
	DBGPRINT((
	    DBG_SS_ERROR,
	    "ReadFile read %u bytes, requested %u\n",
	    cbRead,
	    pth->cbSize));
	_JumpError(hr, error, "ReadFile(cbRead)");
    }
    hr = S_OK;

error:
    if (S_OK != hr && NULL != *ppb)
    {
	LocalFree(*ppb);
	*ppb = NULL;
    }
    return(hr);
}


BOOL
DumpKMSTag(
    IN TagHeader const *pth)
{
    WCHAR const *pwsz;
    WCHAR awctag[20];

    pwsz = NULL;
    switch (pth->tag)
    {
	case KMS_LOCKBOX_TAG:      pwsz = L"KMS_LOCKBOX_TAG";      break;
	case KMS_SIGNING_CERT_TAG: pwsz = L"KMS_SIGNING_CERT_TAG"; break;
	case KMS_SIGNATURE_TAG:    pwsz = L"KMS_SIGNATURE_TAG";    break;
	case KMS_USER_RECORD_TAG:  pwsz = L"KMS_USER_RECORD_TAG";  break;
	default:
	    swprintf(awctag, L"%u", pth->tag);
	    pwsz = awctag;
	    break;
    }
    if (1 < g_fVerbose)
    {
	wprintf(
	    L"%ws: %x (%u) %ws\n",
	    pwsz,
	    pth->cbSize,
	    pth->cbSize,
	    myLoadResourceString(IDS_BYTES));		// "Bytes"
    }
    return(pwsz != awctag);	// TRUE if tag is valid
}


HRESULT
VerifyKMSExportFile(
    IN HANDLE hFile,
    IN DWORD cbFile,
    OUT CERT_CONTEXT const **ppccSigner)
{
    HRESULT hr;
    DWORD cbRemain;
    DWORD cbRead;
    TagHeader th;
    BYTE *pb = NULL;
    CERT_CONTEXT const *pccSigner = NULL;
    HCRYPTPROV hProv = NULL;
    HCRYPTHASH hHash = NULL;
    HCRYPTKEY hkeyPub = NULL;
    BOOL fVerified = FALSE;
    WCHAR *pwszSubject = NULL;

    *ppccSigner = NULL;

    if (!CryptAcquireContext(
			&hProv,
			NULL,		// pszContainer
			NULL,		// pszProvider
			PROV_RSA_FULL,
			CRYPT_VERIFYCONTEXT))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptAcquireContext");
    }
    if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptCreateHash");
    }

    cbRemain = cbFile;
    while (0 < cbRemain)
    {
	fVerified = FALSE;
	CSASSERT(NULL == pb);
	hr = ReadTaggedBlob(hFile, cbRemain, &th, &pb);
	_JumpIfError(hr, error, "ReadTaggedBlob");
	
	if (!DumpKMSTag(&th))
	{
	    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "invalid tag");
	}

	switch (th.tag)
	{
	    case KMS_SIGNING_CERT_TAG:

		if (g_fVerbose || g_fSplitASN)
		{
		    hr = cuDumpAsnBinaryQuiet(pb, th.cbSize, MAXDWORD);
		    _JumpIfError(hr, error, "cuDumpAsnBinaryQuiet");
		}

		if (NULL != pccSigner)
		{
		    hr = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
		    _JumpError(hr, error, "too many signers");
		}
		pccSigner = CertCreateCertificateContext(
						    X509_ASN_ENCODING,
						    pb,
						    th.cbSize);
		if (NULL == pccSigner)
		{
		    hr = myHLastError();
		    _JumpError(hr, error, "CertCreateCertificateContext");
		}
		hr = myCertNameToStr(
			    X509_ASN_ENCODING,
			    &pccSigner->pCertInfo->Subject,
			    CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG,
			    &pwszSubject);
		_PrintIfError(hr, "myCertNameToStr");
		wprintf(L"Processing KMS exports from:");
		wprintf(L"\n    %ws\n\n", pwszSubject);
		break;

	    case KMS_SIGNATURE_TAG:
		if (NULL != hkeyPub)
		{
		    _JumpError(hr, error, "too many signatures");
		}
		if (NULL == pccSigner)
		{
		    hr = TRUST_E_NO_SIGNER_CERT;
		    _JumpError(hr, error, "no signer");
		}
		if (!CryptImportPublicKeyInfo(
				hProv,
				X509_ASN_ENCODING,
				&pccSigner->pCertInfo->SubjectPublicKeyInfo,
				&hkeyPub))
		{
		    hr = myHLastError();
		    _JumpError(hr, error, "CryptImportPublicKeyInfo");
		}
		if (!CryptVerifySignature(
				hHash,
				pb,
				th.cbSize,
				hkeyPub,
				NULL,
				0))
		{
		    hr = myHLastError();
		    _JumpError(hr, error, "CryptVerifySignature");
		}
		fVerified = TRUE;
		wprintf(L"KMS export file signature verifies\n");
		break;

	    default:
		if (!CryptHashData(hHash, (BYTE *) &th, sizeof(th), 0))
		{
		    hr = myHLastError();
		    _JumpError(hr, error, "CryptHashData");
		}
		if (!CryptHashData(hHash, pb, th.cbSize, 0))
		{
		    hr = myHLastError();
		    _JumpError(hr, error, "CryptHashData");
		}
		break;
	}
	LocalFree(pb);
	pb = NULL;
	CSASSERT(cbRemain >= sizeof(th) + sizeof(th.cbSize));
	cbRemain -= sizeof(th) + th.cbSize;
    }
    if (!fVerified)
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
        _JumpError(hr, error, "unsigned data");
    }
    hr = S_OK;

error:
    if (NULL != pwszSubject)
    {
	LocalFree(pwszSubject);
    }
    if (NULL != pb)
    {
	LocalFree(pb);
    }
    if (NULL != hkeyPub)
    {
	CryptDestroyKey(hkeyPub);
    }
    if (NULL != hHash)
    {
	CryptDestroyHash(hHash);
    }
    if (NULL != hProv)
    {
	CryptReleaseContext(hProv, 0);
    }
    return(hr);
}


HRESULT
myEncryptPrivateKey(
    IN CERT_CONTEXT const *pccXchg,
    IN BYTE const *pbKey,
    IN DWORD cbKey,
    OUT BYTE **ppbKeyEncrypted,
    OUT DWORD *pcbKeyEncrypted)
{
    HRESULT hr;
    ALG_ID rgalgId[] = { CALG_3DES, CALG_RC4, CALG_RC2 };
    DWORD i;

    *ppbKeyEncrypted = NULL;

    hr = CRYPT_E_NOT_FOUND;
    for (i = 0; i < ARRAYSIZE(rgalgId); i++)
    {
	// encryt into pkcs7

	hr = myCryptEncryptMessage(
			    rgalgId[i],
			    1,			// cCertRecipient
			    &pccXchg,		// rgCertRecipient
			    pbKey,
			    cbKey,
			    NULL,		// hCryptProv
			    ppbKeyEncrypted,
			    pcbKeyEncrypted);
	if (S_OK == hr)
	{
	    break;		// done
	}
	_PrintError2(hr, "myCryptEncryptMessage", hr);
    }
    _JumpIfError(hr, error, "myCryptEncryptMessage");

error:
    return(hr);
}


#define CB_IV	8

typedef struct _KMSSTATS {
    DWORD cRecUser;

    DWORD cCertWithoutKeys;
    DWORD cCertTotal;
    DWORD cCertNotSaved;
    DWORD cCertAlreadySaved;
    DWORD cCertSaved;
    DWORD cCertSavedForeign;

    DWORD cKeyTotal;
    DWORD cKeyNotSaved;
    DWORD cKeyAlreadySaved;
    DWORD cKeySaved;
    DWORD cKeySavedOverwrite;
} KMSSTATS;


HRESULT
ArchiveCertAndKey(
    IN DISPATCHINTERFACE *pdiAdmin,
    IN CERT_CONTEXT const *pccXchg,
    IN BYTE const *pbCert,
    IN DWORD cbCert,
    IN BYTE const *pbKey,
    IN DWORD cbKey,
    IN OUT KMSSTATS *pkmsStats)
{
    HRESULT hr;
    LONG RequestId;
    BYTE *pbKeyEncrypted = NULL;
    DWORD cbKeyEncrypted;
    CERT_CONTEXT const *pcc = NULL;
    BYTE abHash[CBMAX_CRYPT_HASH_LEN];
    DWORD cbHash;
    BSTR strHash = NULL;
    BOOL fCertSaved = FALSE;
    DWORD ids;
    
    pcc = CertCreateCertificateContext(X509_ASN_ENCODING, pbCert, cbCert);
    if (NULL == pcc)
    {
	hr = myHLastError();
	_JumpError(hr, error, "CertCreateCertificateContext");
    }
    ids = 0;
    hr = Admin_ImportCertificate(
			pdiAdmin,
			g_pwszConfig,
			(WCHAR const *) pbCert,
			cbCert,
			CR_IN_BINARY,
			&RequestId);
    if (g_fForce &&
	S_OK != hr &&
	HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS) != hr)
    {
	hr = Admin_ImportCertificate(
			    pdiAdmin,
			    g_pwszConfig,
			    (WCHAR const *) pbCert,
			    cbCert,
			    ICF_ALLOWFOREIGN | CR_IN_BINARY,
			    &RequestId);
	if (S_OK == hr)
	{
	    pkmsStats->cCertSavedForeign++;
	    ids = IDS_IMPORT_CERT_FOREIGN; // "Imported foreign certificate"
	}
    }
    if (HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS) != hr)
    {
	_JumpIfError2(hr, error, "Admin_ImportCertificate", NTE_BAD_SIGNATURE);

	//wprintf(L"RequestId = %u\n", RequestId);
	pkmsStats->cCertSaved++;
	if (0 == ids)
	{
	    ids = IDS_IMPORT_CERT_DOMESTIC;	// "Imported certificate"
	}
    }
    else
    {
	RequestId = MAXDWORD;
	pkmsStats->cCertAlreadySaved++;
	ids = IDS_IMPORT_CERT_EXISTS;		// "Certificate exists"

	cbHash = sizeof(abHash);
	if (!CertGetCertificateContextProperty(
					pcc,
					CERT_SHA1_HASH_PROP_ID,
					abHash,
					&cbHash))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CertGetCertificateContextProperty");
	}
	hr = MultiByteIntegerToBstr(TRUE, cbHash, abHash, &strHash);
	_JumpIfError(hr, error, "MultiByteIntegerToBstr");
    }
    fCertSaved = TRUE;
    if (g_fVerbose)
    {
	wprintf(myLoadResourceString(ids));
	wprintf(wszNewLine);
    }
    else
    {
	wprintf(L".");
    }

    hr = myEncryptPrivateKey(
		    pccXchg,
		    pbKey,
		    cbKey,
		    &pbKeyEncrypted,
		    &cbKeyEncrypted);
    _JumpIfError(hr, error, "myEncryptPrivateKey");

    ids = 0;
    hr = Admin2_ImportKey(
		    pdiAdmin,
		    g_pwszConfig,
		    RequestId,
		    strHash,
		    CR_IN_BINARY,
		    (WCHAR const *) pbKeyEncrypted,
		    cbKeyEncrypted);
    if (g_fForce && HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS) == hr)
    {
	hr = Admin2_ImportKey(
			pdiAdmin,
			g_pwszConfig,
			RequestId,
			strHash,
			IKF_OVERWRITE | CR_IN_BINARY,
			(WCHAR const *) pbKeyEncrypted,
			cbKeyEncrypted);
	if (S_OK == hr)
	{
	    pkmsStats->cKeySavedOverwrite++;
	    ids = IDS_IMPORT_KEY_REPLACED;	// "Archived key replaced"
	}
    }
    if (HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS) != hr)
    {
	_JumpIfError(hr, error, "Admin2_ImportKey");

	pkmsStats->cKeySaved++;
	if (0 == ids)
	{
	    ids = IDS_IMPORT_KEY_SAVED;	// "Archived key"
	}
    }
    else
    {
	pkmsStats->cKeyAlreadySaved++;
	ids = IDS_IMPORT_KEY_EXISTS;	// "Key already archived"
    }
    if (g_fVerbose)
    {
	wprintf(myLoadResourceString(ids));
	wprintf(wszNewLine);
    }
    else
    {
	wprintf(L".");
    }
    hr = S_OK;

error:
    if (S_OK != hr)
    {
	cuPrintErrorMessageText(hr);
	if (!fCertSaved)
	{
	    pkmsStats->cCertNotSaved++;
	}
	pkmsStats->cKeyNotSaved++;
    }
    if (NULL != pbKeyEncrypted)
    {
	LocalFree(pbKeyEncrypted);
    }
    if (NULL != strHash)
    {
	SysFreeString(strHash);
    }
    if (NULL != pcc)
    {
	CertFreeCertificateContext(pcc);
    }
    return(hr);
}


HRESULT
ImportOneKMSUser(
    IN DISPATCHINTERFACE *pdiAdmin,
    IN CERT_CONTEXT const *pccXchg,
    IN BYTE const *pbRecUser,
    IN DWORD cbRecUser,
    IN HCRYPTKEY hkeySym,
    IN OUT KMSSTATS *pkmsStats)
{
    HRESULT hr;
    BYTE const *pbT = pbRecUser;
    WCHAR *pwszUser = NULL;
    DWORD cbT = cbRecUser;
    DWORD cb;
    DWORD dw;
    CLSID clsid;
    WCHAR *pwszGUID = NULL;
    BYTE *pbKeyASN = NULL;
    DWORD cbKeyASN;
    DWORD cbStream;

    pkmsStats->cRecUser++;

    // Get the user's directory GUID

    CopyMemory(&clsid, pbT, sizeof(clsid));

    hr = myCLSIDToWsz(&clsid, &pwszGUID);
    _JumpIfError(hr, error, "myCLSIDToWsz");

    pbT += sizeof(GUID);
    cbT -= sizeof(GUID);

    // Get the user's name length

    hr = GetMarshaledDword(TRUE, &pbT, &cbT, &cb);
    _JumpIfError(hr, error, "GetMarshaledDword");

    pwszUser = (WCHAR *) LocalAlloc(
				LMEM_FIXED,
				((cb / sizeof(WCHAR)) + 1) * sizeof(WCHAR));
    if (NULL == pwszUser)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    CopyMemory(pwszUser, pbT, cb);
    pwszUser[cb / sizeof(WCHAR)] = L'\0';

    if (g_fVerbose)
    {
	wprintf(L"\n----------------------\n");
    }
    if (g_fVerbose)
    {
	wprintf(L"User: %ws -- %ws\n", pwszUser, pwszGUID);
    }

    pbT += cb;
    cbT -= cb;

    // for each User cert:

    while (0 < cbT)
    {
	DWORD CertStatus;
	FILETIME ftRevoke;
	BYTE const *pbCert;
	DWORD cbCert;
	    
	if (g_fVerbose)
	{
	    wprintf(wszNewLine);
	}
	hr = GetMarshaledDword(FALSE, &pbT, &cbT, &CertStatus);
	_JumpIfError(hr, error, "GetMarshaledDword");

	if (1 < g_fVerbose)
	{
	    wprintf(wszNewLine);
	    cuRegPrintDwordValue(
			    TRUE,
			    wszKMSCERTSTATUS,
			    wszKMSCERTSTATUS,
			    CertStatus);
	}

	hr = GetMarshaledDword(TRUE, &pbT, &cbT, &cb);
	_JumpIfError(hr, error, "GetMarshaledDword");

	// Dump one user cert:

	pbCert = pbT;
	cbCert = cb;
	if (g_fSplitASN)
	{
	    wprintf(wszNewLine);
	    hr = cuDumpAsnBinaryQuiet(pbCert, cbCert, MAXDWORD);
	    _JumpIfError(hr, error, "cuDumpAsnBinaryQuiet");
	}

	pbT += cb;
	cbT -= cb;

	// Get the revocation date (KMS export date):

	hr = GetMarshaledDword(
			FALSE,
			&pbT,
			&cbT,
			&ftRevoke.dwLowDateTime);
	_JumpIfError(hr, error, "GetMarshaledDword");

	hr = GetMarshaledDword(
			FALSE,
			&pbT,
			&cbT,
			&ftRevoke.dwHighDateTime);
	_JumpIfError(hr, error, "GetMarshaledDword");

	if (g_fVerbose)
	{
	    hr = cuDumpFileTime(IDS_REVOCATIONDATE, NULL, &ftRevoke);
	    _PrintIfError(hr, "cuDumpFileTime");
	}

	// Only encryption certs have archived keys:

	if (0 == (CERTFLAGS_SEALING & CertStatus))
	{
	    pkmsStats->cCertWithoutKeys++;
	    if (g_fVerbose)
	    {
		wprintf(myLoadResourceString(IDS_IMPORT_CERT_SKIPPED_SIGNING)); // "Ignored signing certificate"
		wprintf(wszNewLine);
	    }
	    continue;
	}
	pkmsStats->cCertTotal++;
	pkmsStats->cKeyTotal++;

	// get encrypted private key size

	hr = GetMarshaledDword(TRUE, &pbT, &cbT, &cb);
	_JumpIfError(hr, error, "GetMarshaledDword");
	
	// get 8 byte RC2 IV 

	if (1 < g_fVerbose)
	{
	    wprintf(L"IV:\n");
	    DumpHex(
		DH_NOADDRESS | DH_NOTABPREFIX | DH_NOASCIIHEX | 4,
		pbT,
		CB_IV);
	}

	if (NULL != hkeySym)
	{
	    // Set IV

	    if (!CryptSetKeyParam(
				hkeySym,
				KP_IV,
				const_cast<BYTE *>(pbT),
				0))
	    {        
		hr = GetLastError();
		_JumpIfError(hr, error, "CryptSetKeyParam");
	    }
	}
	pbT += CB_IV;
	cbT -= CB_IV;
	cb -= CB_IV;

	if (1 < g_fVerbose)
	{
	    wprintf(wszNewLine);
	    wprintf(L"Encrypted key:\n");
	    DumpHex(0, pbT, cb);
	}
	
	// decrypt key using hkeySym
	// in-place decode is Ok because the size of the
	// original data is always less than or equal to that
	// of the encrypted data 
	
	cbStream = cb;	// save off the real stream size first
	if (NULL != hkeySym)
	{
	    cbKeyASN = cb;
	    pbKeyASN = (BYTE *) LocalAlloc(LMEM_FIXED, cb);
	    if (NULL == pbKeyASN)
	    {
		hr = E_OUTOFMEMORY;
		_JumpError(hr, error, "LocalAlloc");
	    }
	    CopyMemory(pbKeyASN, pbT, cbKeyASN);
	    if (!CryptDecrypt(hkeySym, NULL, TRUE, 0, pbKeyASN, &cb))
	    {
		hr = GetLastError();
		_PrintError(hr, "CryptDecrypt");
	    }
	    else
	    {
		BYTE *pbKey;
		DWORD cbKey;

		if (1 < g_fVerbose)
		{
		    wprintf(wszNewLine);
		    wprintf(L"Decrypted key:\n");
		    DumpHex(0, pbKeyASN, cb);
		}

		hr = myDecodeKMSRSAKey(pbKeyASN, cbKeyASN, &pbKey, &cbKey);
		_JumpIfError(hr, error, "myDecodeKMSRSAKey");

		hr = myVerifyKMSKey(pbCert, cbCert, pbKey, cbKey);
		_PrintIfError(hr, "myVerifyKMSKey");

		hr = ArchiveCertAndKey(
				pdiAdmin,
				pccXchg,
				pbCert,
				cbCert,
				pbKey,
				cbKey,
				pkmsStats);
		_PrintIfError2(hr, "ArchiveCertAndKey", NTE_BAD_SIGNATURE);

		ZeroMemory(pbKey, cbKey);	// Key material
		LocalFree(pbKey);
	    }
	    ZeroMemory(pbKeyASN, cbKeyASN);	// Key material
	    LocalFree(pbKeyASN);
	    pbKeyASN = NULL;
	}

	// skip cbStream bytes, not cb

	pbT += cbStream;
	cbT -= cbStream;
    }
    hr = S_OK;

error:
    if (NULL != pwszGUID)
    {
	LocalFree(pwszGUID);
    }
    if (NULL != pbKeyASN)
    {
	ZeroMemory(pbKeyASN, cbKeyASN);	// Key material
	LocalFree(pbKeyASN);
    }
    return(hr);
}


HRESULT
GetCAXchgCert(
    IN DISPATCHINTERFACE *pdiAdmin,
    OUT CERT_CONTEXT const **ppccXchg)
{
    HRESULT hr;
    BSTR strCert = NULL;
    
    *ppccXchg = NULL;

    hr = Admin2_GetCAProperty(
			pdiAdmin,
			g_pwszConfig,
			CR_PROP_CAXCHGCERT,
			0,			// PropIndex
			PROPTYPE_BINARY,
			CR_OUT_BINARY,
			&strCert);
    _JumpIfError(hr, error, "Admin2_GetCAProperty");

    *ppccXchg = CertCreateCertificateContext(
					X509_ASN_ENCODING,
					(BYTE const *) strCert,
					SysStringByteLen(strCert));
    if (NULL == *ppccXchg)
    {
	hr = myHLastError();
	_JumpError(hr, error, "CertCreateCertificateContext");
    }
    hr = S_OK;

error:
    if (NULL != strCert)
    {
	SysFreeString(strCert);
    }
    return(hr);
}


typedef struct _KMSMAP {
    DWORD dwFieldOffset;
    DWORD idMsg;
} KMSMAP;


KMSMAP g_akmUsers[] = {
  { FIELD_OFFSET(KMSSTATS, cRecUser), IDS_KMS_USERS, },
};

KMSMAP g_akmCerts[] = {
  { FIELD_OFFSET(KMSSTATS, cCertWithoutKeys),  IDS_KMS_CERTS_SKIPPED, },
  { FIELD_OFFSET(KMSSTATS, cCertTotal),        IDS_KMS_CERTS_TOTAL, },
  { FIELD_OFFSET(KMSSTATS, cCertSavedForeign), IDS_KMS_CERTS_FOREIGN, },
  { FIELD_OFFSET(KMSSTATS, cCertAlreadySaved), IDS_KMS_CERTS_ALREADYSAVED, },
  { FIELD_OFFSET(KMSSTATS, cCertSaved),        IDS_KMS_CERTS_SAVED, },
  { FIELD_OFFSET(KMSSTATS, cCertNotSaved),     IDS_KMS_CERTS_NOTSAVED, },
};

KMSMAP g_akmKeys[] = {
  { FIELD_OFFSET(KMSSTATS, cKeyTotal),          IDS_KMS_KEYS_TOTAL, },
  { FIELD_OFFSET(KMSSTATS, cKeyAlreadySaved),   IDS_KMS_KEYS_ALREADYSAVED, },
  { FIELD_OFFSET(KMSSTATS, cKeySavedOverwrite), IDS_KMS_KEYS_UPDATED, },
  { FIELD_OFFSET(KMSSTATS, cKeySaved),          IDS_KMS_KEYS_SAVED, },
  { FIELD_OFFSET(KMSSTATS, cKeyNotSaved),       IDS_KMS_KEYS_NOTSAVED, },
};


VOID
DumpKMSMap(
    IN KMSSTATS const *pkmsStats,
    IN KMSMAP const *pkm,
    IN DWORD ckm)
{
    DWORD i;
    BOOL fFirst = TRUE;
    DWORD count;

    for (i = 0; i < ckm; i++)
    {
	count = *(DWORD *) Add2ConstPtr(pkmsStats, pkm[i].dwFieldOffset);
	if (g_fVerbose || 0 != count)
	{
	    if (fFirst)
	    {
		wprintf(wszNewLine);
		fFirst = FALSE;
	    }
	    wprintf(myLoadResourceString(pkm[i].idMsg));
	    wprintf(L": %u\n", count);
	}
    }
}


VOID
DumpKMSStats(
    IN KMSSTATS const *pkmsStats)
{
    DumpKMSMap(pkmsStats, g_akmUsers, ARRAYSIZE(g_akmUsers));
    DumpKMSMap(pkmsStats, g_akmCerts, ARRAYSIZE(g_akmCerts));
    DumpKMSMap(pkmsStats, g_akmKeys, ARRAYSIZE(g_akmKeys));
}


HRESULT
ImportKMSExportedUsers(
    IN HANDLE hFile,
    IN DWORD cbFile,
    IN HCRYPTPROV hProvKMS,
    IN HCRYPTKEY hkeyKMS)
{
    HRESULT hr;
    DWORD cbRemain;
    DWORD cbRead;
    TagHeader th;
    BYTE *pb = NULL;
    HCRYPTKEY hkeySym = NULL;
    KMSSTATS kmsStats;
    DISPATCHINTERFACE diAdmin;
    BOOL fMustRelease = FALSE;
    CERT_CONTEXT const *pccXchg = NULL;

    ZeroMemory(&kmsStats, sizeof(kmsStats));

    hr = Admin_Init(g_DispatchFlags, &diAdmin);
    _JumpIfError(hr, error, "Admin_Init");

    fMustRelease = TRUE;

    hr = GetCAXchgCert(&diAdmin, &pccXchg);
    _JumpIfError(hr, error, "GetCAXchgCert");

    cbRemain = cbFile;
    while (0 < cbRemain)
    {
	CSASSERT(NULL == pb);
	hr = ReadTaggedBlob(hFile, cbRemain, &th, &pb);
	_JumpIfError(hr, error, "ReadTaggedBlob");

	if (!DumpKMSTag(&th))
	{
	    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "invalid tag");
	}

	switch (th.tag)
	{
	    case KMS_LOCKBOX_TAG:
            {
		if (1 < g_fVerbose)
		{
		    hr = cuDumpPrivateKeyBlob(pb, th.cbSize, FALSE);
		    _PrintIfError(hr, "cuDumpPrivateKeyBlob");
		}

                // only need one symmetric key per file

                if (NULL == hkeySym)
                {
		    // 0x0000660c ALG_ID CALG_RC2_128
		    //
		    // CALG_RC2_128:
		    //  ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC2_128
		    //
		    // CALG_CYLINK_MEK:
		    //  ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_CYLINK_MEK
		    //
		    // UGH!  Exchange's CALG_RC2_128 #define collides with
		    // wincrypt.h's CALG_CYLINK_MEK -- fix it up to be the
		    // correct CALG_RC2 algid from wincrypt.h.

		    ((PUBLICKEYSTRUC *) pb)->aiKeyAlg = CALG_RC2;

		    // dump the fixed-up blob

		    if (1 < g_fVerbose)
		    {
			hr = cuDumpPrivateKeyBlob(pb, th.cbSize, FALSE);
			_PrintIfError(hr, "cuDumpPrivateKeyBlob");
		    }

		    // import 128 bit key 

		    if (!CryptImportKey(
				    hProvKMS,
				    pb,
				    th.cbSize,
				    hkeyKMS,
				    0,
				    &hkeySym))
		    {
			hr = myHLastError();
			_PrintError(hr, "CryptImportKey");
			wprintf(L"Failed to import symmetric key\n");
		    }
		    else
		    {
			// We found the right lockbox.  Effective keylen is
			// still 40 bits in our CSP, reset to 128

			DWORD dwEffectiveKeylen = 128;

			if (!CryptSetKeyParam(
					hkeySym,
					KP_EFFECTIVE_KEYLEN,
					(BYTE *) &dwEffectiveKeylen,
					0))
			{
			    hr = myHLastError();
			    _JumpError(hr, error, "CryptSetKeyParam(KP_EFFECTIVE_KEYLEN)");
			}
			wprintf(L"Lock box opened, symmetric key successfully decrypted\n");
		    }
		}
		break;
	    }

	    case KMS_USER_RECORD_TAG:
		hr = ImportOneKMSUser(
				&diAdmin,
				pccXchg,
				pb,
				th.cbSize,
				hkeySym,
				&kmsStats);
		_JumpIfError(hr, error, "ImportOneKMSUser");

		break;

	    default:
		break;
	}

	LocalFree(pb);
	pb = NULL;
	CSASSERT(cbRemain >= sizeof(th) + sizeof(th.cbSize));
	cbRemain -= sizeof(th) + th.cbSize;
    }

    if (!g_fVerbose)
    {
	wprintf(wszNewLine);
    }
    DumpKMSStats(&kmsStats);
    hr = S_OK;

error:
    if (NULL != pccXchg)
    {
	CertFreeCertificateContext(pccXchg);
    }
    if (fMustRelease)
    {
	Admin_Release(&diAdmin);
    }
    if (NULL != hkeySym)
    {
	CryptDestroyKey(hkeySym);
    }
    return(hr);
}


HRESULT
LoadKMSCert(
    IN WCHAR const *pwszCertId,
    OUT CERT_CONTEXT const **ppccKMS,
    OUT HCRYPTPROV *phProvKMS,
    OUT HCRYPTKEY *phkeyKMS)
{
    HRESULT hr;
    CRYPT_KEY_PROV_INFO *pkpiKMS = NULL;
    DWORD cbkpiKMS;
    BYTE *pbKey = NULL;
    DWORD cbKey;
    HCRYPTKEY hkeyKMSSig = NULL;

    *ppccKMS = NULL;
    *phProvKMS = NULL;
    *phkeyKMS = NULL;

    hr = myGetCertificateFromPicker(
			    g_hInstance,
			    NULL,		// hwndParent
			    IDS_GETCERT_TITLE,	// "Certificate List"
			    IDS_GETDECRYPTCERT_SUBTITLE,

			    // dwFlags: HKLM+HKCU My store
			    CUCS_MYSTORE |
				CUCS_MACHINESTORE |
				CUCS_USERSTORE |
				CUCS_PRIVATEKEYREQUIRED |
				(g_fCryptSilent? CUCS_SILENT : 0),
			    pwszCertId,
			    0,			// cStore
			    NULL,		// rghStore
			    0,			// cpszObjId
			    NULL,		// apszObjId
			    ppccKMS);		// ppCert
    _JumpIfError(hr, error, "myGetCertificateFromPicker");

    if (NULL == *ppccKMS)
    {
	hr = ERROR_CANCELLED;
	_JumpError(hr, error, "myGetCertificateFromPicker");
    }

    if (!myCertGetCertificateContextProperty(
			*ppccKMS,
			CERT_KEY_PROV_INFO_PROP_ID,
			CERTLIB_USE_LOCALALLOC,
			(VOID **) &pkpiKMS,
			&cbkpiKMS))
    {
	hr = myHLastError();
	_JumpError(hr, error, "myCertGetCertificateContextProperty");
    }
    if (g_fVerbose)
    {
	wprintf(L"CryptAcquireContext(%ws)\n", pkpiKMS->pwszContainerName);
    }
    if (!CryptAcquireContext(
			phProvKMS,
			pkpiKMS->pwszContainerName,
			pkpiKMS->pwszProvName,
			pkpiKMS->dwProvType,
			pkpiKMS->dwFlags))
    {
	hr = myHLastError();
	wprintf(L"CryptAcquireContext() --> %x\n", hr);
	_JumpError(hr, error, "CryptAcquireContext");
    }

    if (!CryptGetUserKey(*phProvKMS, AT_KEYEXCHANGE, phkeyKMS))
    {
	hr = myHLastError();
	if (hr != NTE_NO_KEY)
	{
	    _JumpError(hr, error, "CryptGetUserKey");
	}

	if (!CryptGetUserKey(*phProvKMS, AT_SIGNATURE, &hkeyKMSSig))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CryptGetUserKey - sig");
	}

	// UGH! migrate from AT_SIGNATURE container!

	cbKey = 0;
	hr = myCryptExportKey(
			hkeyKMSSig,	// hKey
			NULL,		// hKeyExp
			PRIVATEKEYBLOB,	// dwBlobType
			0,		// dwFlags
			&pbKey,
			&cbKey);
	_JumpIfError(hr, error, "myCryptExportKey");

	// UGH! fix up the algid to signature...

	((PUBLICKEYSTRUC *) pbKey)->aiKeyAlg = CALG_RSA_KEYX;
	
	// and re-import it

	if (!CryptImportKey(
			*phProvKMS,
			pbKey,
			cbKey,
			NULL,
			0,
			phkeyKMS))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CryptImportKey");
	}
	wprintf(L"Moved AT_SIGNATURE key to AT_KEYEXCHANGE\n");
    }
    hr = S_OK;

error:
    if (S_OK != hr)
    {
	if (NULL != *ppccKMS)
	{
	    CertFreeCertificateContext(*ppccKMS);
	    *ppccKMS = NULL;
	}
	if (NULL != *phProvKMS)
	{
	    CryptReleaseContext(*phProvKMS, 0);
	    *phProvKMS = NULL;
	}
    }
    if (NULL != pbKey)
    {
        LocalFree(pbKey); 
    }
    if (NULL != pkpiKMS)
    {
        LocalFree(pkpiKMS); 
    }
    if (NULL != hkeyKMSSig)
    {
        CryptDestroyKey(hkeyKMSSig);
    }
    return(hr);
}


HRESULT
ImportOnePFXCert(
    IN DISPATCHINTERFACE *pdiAdmin,
    IN CERT_CONTEXT const *pccXchg,
    IN CERT_CONTEXT const *pCert,
    IN OUT KMSSTATS *pkmsStats)
{
    HRESULT hr;
    CRYPT_KEY_PROV_INFO *pkpi = NULL;
    HCRYPTPROV hProv = NULL;
    HCRYPTKEY hKey = NULL;
    BYTE *pbKey = NULL;
    DWORD cbKey;

    hr = myCertGetKeyProviderInfo(pCert, &pkpi);
    if (S_OK != hr)
    {
	_PrintError(hr, "myCertGetKeyProviderInfo");
	pkmsStats->cCertWithoutKeys++;
	hr = S_OK;
	goto error;
    }
    pkmsStats->cCertTotal++;
    if (!CryptAcquireContext(
			&hProv,
			pkpi->pwszContainerName,
			pkpi->pwszProvName,
			pkpi->dwProvType,
			0))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptAcquireContext");
    }
    if (!CryptGetUserKey(hProv, pkpi->dwKeySpec, &hKey))
    {
	hr = myHLastError();
	_JumpIfError(hr, error, "CryptGetUserKey");
    }
    hr = myCryptExportPrivateKey(hKey, &pbKey, &cbKey);
    _JumpIfError(hr, error, "myCryptExportPrivateKey");

    pkmsStats->cKeyTotal++;

    hr = myVerifyKMSKey(
		pCert->pbCertEncoded,
		pCert->cbCertEncoded,
		pbKey,
		cbKey);
    _JumpIfError(hr, error, "myVerifyKMSKey");

    hr = ArchiveCertAndKey(
		    pdiAdmin,
		    pccXchg,
		    pCert->pbCertEncoded,
		    pCert->cbCertEncoded,
		    pbKey,
		    cbKey,
		    pkmsStats);
    _JumpIfError(hr, error, "ArchiveCertAndKey");

error:
    if (NULL != pbKey)
    {
	ZeroMemory(pbKey, cbKey);	// Key material
	LocalFree(pbKey);
    }
    if (NULL != hProv)
    {
	CryptReleaseContext(hProv, 0);
    }
    if (NULL != pkpi)
    {
	LocalFree(pkpi);
    }
    return(hr);
}


HRESULT
ImportKMSPFXFile(
    IN WCHAR const *pwszfn)
{
    HRESULT hr;
    CRYPT_DATA_BLOB pfx;
    WCHAR wszPassword[MAX_PATH];
    WCHAR const *pwszPassword;
    CERT_CONTEXT const *pccXchg = NULL;
    HCERTSTORE hStorePFX = NULL;
    CERT_CONTEXT const *pCert = NULL;
    DISPATCHINTERFACE diAdmin;
    BOOL fMustRelease = FALSE;
    KMSSTATS kmsStats;

    ZeroMemory(&kmsStats, sizeof(kmsStats));
    pfx.pbData = NULL;

    hr = DecodeFileW(pwszfn, &pfx.pbData, &pfx.cbData, CRYPT_STRING_ANY);
    if (S_OK != hr)
    {
	cuPrintError(IDS_ERR_FORMAT_DECODEFILE, hr);
	goto error;
    }
    if (!PFXIsPFXBlob(&pfx))
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	_JumpError(hr, error, "not a PFX");
    }

    pwszPassword = g_pwszPassword;
    if (NULL == pwszPassword)
    {
	hr = cuGetPassword(FALSE, wszPassword, ARRAYSIZE(wszPassword));
	_JumpIfError(hr, error, "cuGetPassword");
    }

    hStorePFX = myPFXImportCertStore(&pfx, pwszPassword, CRYPT_EXPORTABLE);
    if (NULL == hStorePFX)
    {
	hr = myHLastError();
	_JumpError(hr, error, "myPFXImportCertStore");
    }

    hr = Admin_Init(g_DispatchFlags, &diAdmin);
    _JumpIfError(hr, error, "Admin_Init");

    fMustRelease = TRUE;

    hr = GetCAXchgCert(&diAdmin, &pccXchg);
    _JumpIfError(hr, error, "GetCAXchgCert");

    while (TRUE)
    {
	pCert = CertEnumCertificatesInStore(hStorePFX, pCert);
	if (NULL == pCert)
	{
	    break;
	}
	hr = ImportOnePFXCert(&diAdmin, pccXchg, pCert, &kmsStats);
	_PrintIfError(hr, "ImportOnePFXCert");
    }
    DumpKMSStats(&kmsStats);
    hr = S_OK;

error:
    if (NULL != hStorePFX)
    {
        myDeleteGuidKeys(hStorePFX, FALSE);
	CertCloseStore(hStorePFX, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    if (NULL != pccXchg)
    {
	CertFreeCertificateContext(pccXchg);
    }
    if (fMustRelease)
    {
	Admin_Release(&diAdmin);
    }
    if (NULL != pfx.pbData)
    {
        LocalFree(pfx.pbData); 
    }
    return(hr);
}


HRESULT
ImportKMSExportFile(
    IN WCHAR const *pwszfnKMS,
    IN WCHAR const *pwszCertId,
    OUT BOOL *pfBadTag)
{
    HRESULT hr;
    CERT_CONTEXT const *pccKMS = NULL;
    HCRYPTPROV hProvKMS = NULL;
    HCRYPTKEY hkeyKMS = NULL;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    DWORD cbFile;
    CERT_CONTEXT const *pccSigner = NULL;

    *pfBadTag = TRUE;
    hFile = CreateFile(
		    pwszfnKMS,
		    GENERIC_READ,
		    FILE_SHARE_READ,
		    NULL,
		    OPEN_EXISTING,
		    FILE_FLAG_SEQUENTIAL_SCAN,
		    NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
	hr = myHLastError();
	_JumpError(hr, error, "CreateFile");
    }

    cbFile = GetFileSize(hFile, NULL);
    if (MAXDWORD == cbFile)
    {
	hr = myHLastError();
	_JumpError(hr, error, "GetFileSize");
    }

    // verify the KMS data signature

    hr = VerifyKMSExportFile(hFile, cbFile, &pccSigner);
    _JumpIfError(hr, error, "VerifyKMSExportFile");

    *pfBadTag = FALSE;
    if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
    {
	hr = myHLastError();
	_JumpError(hr, error, "SetFilePointer");
    }

    // Load the KMS recipient cert to be used to decrypt user keys

    hr = LoadKMSCert(pwszCertId, &pccKMS, &hProvKMS, &hkeyKMS);
    _JumpIfError(hr, error, "LoadKMSCert");

    // import the KMS data

    hr = ImportKMSExportedUsers(hFile, cbFile, hProvKMS, hkeyKMS);
    _JumpIfError(hr, error, "ImportKMSExportedUsers");

error:
    if (NULL != pccKMS)
    {
	CertFreeCertificateContext(pccKMS);
    }
    if (NULL != pccSigner)
    {
	CertFreeCertificateContext(pccSigner);
    }
    if (NULL != hkeyKMS)
    {
       CryptDestroyKey(hkeyKMS);
    }
    if (NULL != hProvKMS)
    {
	CryptReleaseContext(hProvKMS, 0);
    }
    if (INVALID_HANDLE_VALUE != hFile)
    {
        CloseHandle(hFile);
    }
    return(hr);
}


HRESULT
verbImportKMS(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszfnKMS,
    IN WCHAR const *pwszCertId,
    IN WCHAR const *pwszArg3,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    BOOL fBadTag;

    hr = ImportKMSExportFile(pwszfnKMS, pwszCertId, &fBadTag);
    if (S_OK != hr)
    {
	_PrintError(hr, "ImportKMSExportFile");
	if (!fBadTag)
	{
	    goto error;
	}
	hr = ImportKMSPFXFile(pwszfnKMS);
	_JumpIfError(hr, error, "ImportKMSPFXFile");
    }

error:
    return(hr);
}


WCHAR const g_wszProviderNameDefault[] = MS_DEF_PROV_W;
DWORD g_dwProviderType = PROV_RSA_FULL;


HRESULT
EnumKeys(
    IN WCHAR const *pwszProvName,
    IN DWORD dwProvType,
    IN BOOL fSkipKeys,
    OPTIONAL IN WCHAR const *pwszKeyContainerName)
{
    HRESULT hr;
    KEY_LIST *pKeyList = NULL;
    KEY_LIST *pKeyT;
    WCHAR *pwszRevert = NULL;
    CERT_PUBLIC_KEY_INFO *pPubKeyInfoSig = NULL;
    CERT_PUBLIC_KEY_INFO *pPubKeyInfoXchg = NULL;
    WCHAR const *pwszPrefix;

    if (!fSkipKeys)
    {
	hr = csiGetKeyList(
		    dwProvType,		// dwProvType
		    pwszProvName,	// pwszProvName
		    !g_fUserRegistry,	// fMachineKeyset
		    !g_fCryptSilent,	// inverted fSilent: default is Silent!
		    &pKeyList);
	_JumpIfErrorStr(hr, error, "csiGetKeyList", pwszProvName);
    }
    if (fSkipKeys || NULL != pKeyList)
    {
	wprintf(L"%ws:\n", pwszProvName);
    }
    for (pKeyT = pKeyList; NULL != pKeyT; pKeyT = pKeyT->next)
    {
	DWORD dwProvTypeT;

	hr = myRevertSanitizeName(pKeyT->pwszName, &pwszRevert);
	_JumpIfError(hr, error, "myRevertSanitizeName");

	if (NULL == pwszKeyContainerName ||
	    0 == lstrcmpi(pwszKeyContainerName, pwszRevert))
	{
	    wprintf(L"  %ws", pwszRevert);
	    if (g_fVerbose && 0 != lstrcmp(pKeyT->pwszName, pwszRevert))
	    {
		wprintf(L" -- %ws", pKeyT->pwszName);
	    }
	    wprintf(wszNewLine);

	    dwProvTypeT = dwProvType;
	    hr = cuLoadKeys(
			pwszProvName,
			&dwProvTypeT,
			pKeyT->pwszName,
			!g_fUserRegistry,	// fMachineKeyset
			TRUE,
			NULL,
			&pPubKeyInfoSig,
			&pPubKeyInfoXchg);
	    if (S_OK != hr)
	    {
		cuPrintError(IDS_ERR_FORMAT_LOADKEYS, hr);
	    }
	    if (NULL != pPubKeyInfoSig || NULL != pPubKeyInfoXchg)
	    {
		pwszPrefix = g_wszPad4;
		if (NULL != pPubKeyInfoSig)
		{
		    wprintf(L"    AT_SIGNATURE");
		    LocalFree(pPubKeyInfoSig);
		    pPubKeyInfoSig = NULL;
		    pwszPrefix = L", ";
		}
		if (NULL != pPubKeyInfoXchg)
		{
		    wprintf(L"%wsAT_KEYEXCHANGE", pwszPrefix);
		    LocalFree(pPubKeyInfoXchg);
		    pPubKeyInfoXchg = NULL;
		}
		wprintf(wszNewLine);
	    }
	    if (NULL == pwszKeyContainerName)
	    {
		wprintf(wszNewLine);
	    }
	}
	LocalFree(pwszRevert);
	pwszRevert = NULL;
    }
    hr = S_OK;

error:
    if (NULL != pPubKeyInfoSig)
    {
	LocalFree(pPubKeyInfoSig);
    }
    if (NULL != pPubKeyInfoXchg)
    {
	LocalFree(pPubKeyInfoXchg);
    }
    if (NULL != pwszRevert)
    {
	LocalFree(pwszRevert);
    }
    if (NULL != pKeyList)
    {
	csiFreeKeyList(pKeyList);
    }
    return(hr);
}


HRESULT
verbKey(
    IN WCHAR const *pwszOption,
    OPTIONAL IN WCHAR const *pwszKeyContainerName,
    OPTIONAL IN WCHAR const *pwszProvider,
    IN WCHAR const *pwszArg3,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    WCHAR *pwszProvName = NULL;
    DWORD i;
    DWORD dwProvType;
    BOOL fSkipKeys = FALSE;

    if (NULL == pwszProvider)
    {
	pwszProvider = g_wszProviderNameDefault; // default CSP
    }
    else if (0 == lstrcmp(L"*", pwszProvider))
    {
	pwszProvider = NULL;			// all CSPs
    }
    if (NULL != pwszKeyContainerName)
    {
	if (0 == lstrcmp(L"*", pwszKeyContainerName))
	{
	    pwszKeyContainerName = NULL;		// all keys
	}
	else if (0 == lstrcmp(L"-", pwszKeyContainerName))
	{
	    pwszKeyContainerName = NULL;		// all keys
	    fSkipKeys = TRUE;
	}
    }

    if (NULL != pwszProvider)
    {
	hr = csiGetProviderTypeFromProviderName(pwszProvider, &dwProvType);
        _JumpIfErrorStr(hr, error, "csiGetProviderTypeFromProviderName", pwszProvider);
	
	hr = EnumKeys(
		    pwszProvider,
		    dwProvType,
		    fSkipKeys,
		    pwszKeyContainerName);
        _JumpIfErrorStr(hr, error, "EnumKeys", pwszProvider);
    }
    else
    {
	for (i = 0; ; i++)
	{
	    CSASSERT(NULL == pwszProvName);
	    hr = myEnumProviders(i, NULL, 0, &dwProvType, &pwszProvName);
	    if (S_OK != hr)
	    {
		if (HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) == hr ||
		    NTE_FAIL == hr)
		{
		    // no more providers under type, out of i loop
		    break;
		}
 
		// invalid csp entry, skip it
		wprintf(L"Failed to enum CSP at (%ld)\n", i);
	    }
	    else
	    {
		hr = EnumKeys(
			    pwszProvName,
			    dwProvType,
			    fSkipKeys,
			    pwszKeyContainerName);
		_JumpIfErrorStr(hr, error, "EnumKeys", pwszProvName);

		LocalFree(pwszProvName);
		pwszProvName = NULL;
	    }
	}
    }
    hr = S_OK;

error:
    if (NULL != pwszProvName)
    {
	LocalFree(pwszProvName);
    }
    return(hr);
}


HRESULT
cuSanitizeNameWithSuffix(
    IN WCHAR const *pwszName,
    OUT WCHAR **ppwszNameOut)
{
    HRESULT hr;
    WCHAR const *pwszSuffix;
    WCHAR const *pwsz;
    WCHAR *pwszBase = NULL;
    WCHAR *pwszSanitizedName = NULL;
    DWORD cwc;

    pwsz = wcsrchr(pwszName, wcLPAREN);
    pwszSuffix = pwsz;
    if (NULL != pwsz)
    {
	BOOL fSawDigit = FALSE;

	pwsz++;
	while (iswdigit(*pwsz))
	{
	    pwsz++;
	    fSawDigit = TRUE;
	}
	if (fSawDigit &&
	    wcRPAREN == *pwsz &&
	    (L'.' == pwsz[1] || L'\0' == pwsz[1]))
	{
	    cwc = SAFE_SUBTRACT_POINTERS(pwszSuffix, pwszName);

	    pwszBase = (WCHAR *) LocalAlloc(
					LMEM_FIXED,
					(cwc + 1) * sizeof(WCHAR));
	    if (NULL == pwszBase)
	    {
		hr = E_OUTOFMEMORY;
		_JumpError(hr, error, "LocalAlloc");
	    }
	    CopyMemory(pwszBase, pwszName, cwc * sizeof(WCHAR));
	    pwszBase[cwc] = L'\0';
	    pwszName = pwszBase;
	}
	else
	{
	    pwszSuffix = NULL;
	}
    }
    hr = mySanitizeName(pwszName, &pwszSanitizedName);
    _JumpIfError(hr, error, "mySanitizeName");

    if (NULL == pwszSuffix)
    {
	*ppwszNameOut = pwszSanitizedName;
	pwszSanitizedName = NULL;
    }
    else
    {
	*ppwszNameOut = (WCHAR *) LocalAlloc(
				    LMEM_FIXED,
				    (wcslen(pwszSanitizedName) +
					wcslen(pwszSuffix) +
					1) * sizeof(WCHAR));
	if (NULL == *ppwszNameOut)
	{
	    hr = E_OUTOFMEMORY;
	    _JumpError(hr, error, "LocalAlloc");
	}
	wcscpy(*ppwszNameOut, pwszSanitizedName);
	wcscat(*ppwszNameOut, pwszSuffix);
    }
    hr = S_OK;

error:
    if (NULL != pwszSanitizedName)
    {
	LocalFree(pwszSanitizedName);
    }
    if (NULL != pwszBase)
    {
	LocalFree(pwszBase);
    }
    return(hr);
}


HRESULT
verbDelKey(
    IN WCHAR const *pwszOption,
    IN WCHAR const *pwszKeyContainerName,
    OPTIONAL IN WCHAR const *pwszProvider,
    IN WCHAR const *pwszArg3,
    IN WCHAR const *pwszArg4)
{
    HRESULT hr;
    HCRYPTPROV hProv = NULL;
    WCHAR *apwszKeyContainer[3];
    DWORD i, dwProviderType;
    DWORD dwFlags = CRYPT_DELETEKEYSET;

    // If supplied provider is NULL, use the default provider. 
    if (pwszProvider == NULL) 
    {
	pwszProvider = g_wszProviderNameDefault;
    }
    
    apwszKeyContainer[0] = const_cast<WCHAR *>(pwszKeyContainerName);
    apwszKeyContainer[1] = NULL;
    apwszKeyContainer[2] = NULL;

    hr = mySanitizeName(pwszKeyContainerName, &apwszKeyContainer[1]);
    _JumpIfError(hr, error, "mySanitizeName");

    hr = cuSanitizeNameWithSuffix(pwszKeyContainerName, &apwszKeyContainer[2]);
    _JumpIfError(hr, error, "cuSanitizeNameWithSuffix");

    hr = csiGetProviderTypeFromProviderName(pwszProvider, &dwProviderType);
    _JumpIfError(hr, error, "csiGetProviderTypeFromProviderName");
      
    if (g_fCryptSilent)
    {
        dwFlags |= CRYPT_SILENT;
    }

    for (i = 0; i < ARRAYSIZE(apwszKeyContainer); i++)
    {
	if (!myCertSrvCryptAcquireContext(
			    &hProv,
			    apwszKeyContainer[i],
			    pwszProvider,
			    dwProviderType, 
			    dwFlags,
			    !g_fUserRegistry))	// fMachineKeyset
	{
	    hr = myHLastError();
	    _PrintErrorStr2(
			hr,
			"myCertSrvCryptAcquireContext",
			apwszKeyContainer[i],
			hr);
	}
	else
	{
	    DWORD j;
	    
	    wprintf(L"  %ws", apwszKeyContainer[i]);
	    if (g_fVerbose)
	    {
		wprintf(L" --");
		for (j = 0; j < ARRAYSIZE(apwszKeyContainer); j++)
		{
		    wprintf(L" %ws", apwszKeyContainer[j]);
		}
	    }
	    wprintf(wszNewLine);
	    hr = S_OK;
	    break;
	}
    }
    _JumpIfError(hr, error, "myCertSrvCryptAcquireContext");

error:
    for (i = 1; i < ARRAYSIZE(apwszKeyContainer); i++)
    {
	if (NULL != apwszKeyContainer[i])
	{
	    LocalFree(apwszKeyContainer[i]);
	}
    }
    return(hr);
}