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

#include <pch.cpp>

#pragma hdrstop

#include <conio.h>

#include "encode.h"
#include "rsa.h"
#include "md5.h"
#include <wincrypt.h>
#include <certsrv.h>
#include <certca.h>
#include <csdisp.h>
#include "csprop.h"

#define MSTOSEC(ms)	(((ms) + 1000 - 1)/1000)

DWORD g_crdnMax;


HCRYPTPROV g_hMe = NULL;

WCHAR g_wszTestKey[] = L"CertGen_TestKey";


static unsigned char MD5_PRELUDE[] = {
    0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86,
    0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00,
    0x04, 0x10
};

BYTE            g_CAPIPrivateKey[1000];
DWORD           g_cbPrivateKey;
//LPBSAFE_PRV_KEY g_pRSAPrivateKey;
DWORD           g_cbRSAPrivateKey;
LPBSAFE_PUB_KEY g_pRSAPublicKey;
DWORD           g_cbRSAPublicKey;

WCHAR *g_pwszConfig = NULL;

typedef struct {
    DWORD       magic;                  // Should always be RSA2
    DWORD       bitlen;                 // bit size of key
    DWORD       pubexp;                 // public exponent
} EXPORT_PRV_KEY;

BOOL g_fRPC = FALSE;
BOOL g_fRenewal = FALSE;
BOOL g_fSave = FALSE;
BOOL g_fPrintProperties = FALSE;
BOOL g_fDebug = FALSE;
BOOL g_fIgnoreAccessDenied = FALSE;
BOOL g_fTime = FALSE;
BOOL g_fIgnoreError = FALSE;
BOOL g_fAllowDups = FALSE;
BOOL g_fShowTime = FALSE;

LONG g_IntervalCount;
DWORD g_MaximumCount = MAXDWORD;
DWORD g_DispatchFlags = DISPSETUP_COMFIRST;

BOOL IsCharPrintableString(TCHAR chChar);

WCHAR wszUsage[] =
    TEXT("Usage: CertGen [options]\n")
    TEXT("Options are:\n")
    TEXT("  -a                     - ignore denied requests\n")
    TEXT("  -c #                   - generate # certs\n")
    TEXT("  -config server\\CAName - specify CA config string\n")
    TEXT("  -renewal               - generate renewal requests\n")
    TEXT("  -rpc                   - use RPC to connect to server\n")
    TEXT("  -r                     - put request/cert/chain info into test.req/test.crt/testchain.crt\n")
    TEXT("  -t #                   - print time statistics every # certs\n")
    TEXT("  -p                     - print properties from cert created\n")
    TEXT("  -i                     - don't stop on request errors\n")
    TEXT("  -z                     - allow duplicate subject name components\n")
    TEXT("  -m                     - print start/end time\n")
;


HRESULT
SeedRNG(void)
{
    HRESULT hr;
    unsigned int seed;

    if (!CryptGenRandom(g_hMe, sizeof(seed), (BYTE *) &seed))
    {
        hr = myHLastError();
        _JumpError(hr, error, "CryptGenRandom");
    }
    srand(seed);
    hr = S_OK;

error:
    return(hr);

}


HRESULT
GenerateString(
    DWORD cnt,
    BYTE *pbStr)
{
    HRESULT hr;
    DWORD i;
    BYTE *pb;

    hr = SeedRNG();
    _JumpIfError(hr, error, "SeedRNG");

    pb = pbStr;
    for (i = 0; i < cnt; i++)
    {
        do
        {
            *pb = rand() % 0x7f;
        } while (!IsCharPrintableString(*pb));
        pb++;
    }
    *pb = '\0';

    // Turn leading and trailing Blanks into '.' characters?
    if (g_fAllowDups && 0 < cnt)
    {
	if (' ' == *pbStr)
	{
	    *pbStr = '.';
	}
	pb--;
	if (' ' == *pb)
	{
	    *pb = '.';
	}
    }

error:
    return(hr);

}


void
FreeLocalMemory(
    NAMETABLE *pNameTable)
{
    NAMEENTRY *pNameEntry = NULL;
    DWORD i;

    pNameEntry = pNameTable->pNameEntry;
    for (i = 0; i < pNameTable->cnt; i++)
    {
        if (NULL != pNameEntry->pbData)
	{
	    LocalFree(pNameEntry->pbData);
	}
	pNameEntry++;
    }
    LocalFree(pNameTable->pNameEntry);
}


HRESULT
GenerateNameTable(
    NAMETABLE *pNameTable)
{
    HRESULT hr;
    NAMEENTRY *pNameEntryAlloc = NULL;
    NAMEENTRY *pNameEntry;
    DWORD cbString;
    BYTE *pbString;
    DWORD i;
    DWORD j;
    DWORD cRetry;

    hr = SeedRNG();
    _JumpIfError(hr, error, "SeedRNG");

    pNameTable->cnt = rand() % g_crdnMax;	// 0 is Ok

    if (1 < g_fPrintProperties)
    {
	wprintf(L"NumEntries = %u\n", pNameTable->cnt);
    }
    for (i = 0; i < g_crdnSubject; i++)
    {
	g_ardnSubject[i].cbRemain = g_ardnSubject[i].cbMaxConcatenated;
    }

    pNameEntryAlloc = (NAMEENTRY *) LocalAlloc(
				    LMEM_FIXED | LMEM_ZEROINIT,
				    pNameTable->cnt * sizeof(NAMEENTRY));

    if (NULL == pNameEntryAlloc)
    {
        hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    pNameTable->pNameEntry = pNameEntryAlloc;

    for (i = 0; i < pNameTable->cnt; i++)
    {
	RDNENTRY *prdne;

	pNameEntry = &pNameTable->pNameEntry[i];

        for (cRetry = 0; !g_fAllowDups || cRetry < 2 * pNameTable->cnt; cRetry++)
        {
	    pNameEntry->iRDN = rand() % g_crdnSubject;
	    prdne = &g_ardnSubject[pNameEntry->iRDN];

            if (g_fAllowDups)
	    {
		if (2 > prdne->cbRemain)
		{
		    continue;		// Skip if less than 2 characters left
		}
	    }
	    else
            {            
                for (j = 0; j < i; j++)
                {
                    if (pNameEntry->iRDN == pNameTable->pNameEntry[j].iRDN)
                    {
                       break;
                    }
                }
		if (j < i)
		{
		    continue;		// Skip if a disallowed duplicate
		}
            }
	    break;
        }
	if (g_fAllowDups && cRetry >= 2 * pNameTable->cnt)
	{
	    if (1 < g_fPrintProperties)
	    {
		wprintf(L"Reducing NumEntries = %u --> %i\n", pNameTable->cnt, i);
	    }
	    pNameTable->cnt = i;	// too many retries -- reduce count & quit
	    break;
	}

        pNameEntry->pszObjId = prdne->pszObjId;
        pNameEntry->BerTag = prdne->BerTag;

	assert(2 <= prdne->cbRemain);
	do
	{
            cbString = rand() % min(prdne->cbMaxString, prdne->cbRemain);
	} while (0 == cbString);

	// Reduce remaining count by length of string plus separator: "\n"

	if (1 < g_fPrintProperties)
	{
	    wprintf(
		L"  RDN(%u): %hs=%u/%u/%u/",
		i,
		prdne->pszShortName,
		cbString,
		prdne->cbMaxString,
		prdne->cbRemain);
	}
	prdne->cbRemain -= cbString;
	if (0 < prdne->cbRemain)
	{
	    prdne->cbRemain--;
	}

	// Limit each string to (prdne->cbMaxString + 1) chars, including
	// trailing '\0':

	assert(cbString <= prdne->cbMaxString); // leave room for '\0' in DB

	pbString = (BYTE *) LocalAlloc(LMEM_FIXED, cbString + 1);
	if (NULL == pbString)
	{
	    hr = E_OUTOFMEMORY;
	    _JumpError(hr, error, "LocalAlloc");
	}
	hr = GenerateString(cbString, pbString);
	_JumpIfError(hr, error, "GenerateString");

	if (1 < g_fPrintProperties)
	{
	    wprintf(L"%u: \"%hs\"\n", prdne->cbRemain, pbString);
	}
	pNameEntry->cbData = cbString;
	pNameEntry->pbData = pbString;
    }
    pNameEntryAlloc = NULL;

error:
    if (NULL != pNameEntryAlloc)
    {
	FreeLocalMemory(pNameTable);
    }
    return(hr);
}


HRESULT
GenerateTestNameTable(
    NAMETABLE *pNameTable)
{
    HRESULT hr;
    NAMEENTRY *pNameEntryAlloc = NULL;
    NAMEENTRY *pNameEntry;
    DWORD cbString;
    BYTE *pbString;
    DWORD i;
    DWORD j;
    char szTest[2];

    pNameEntryAlloc = (NAMEENTRY *) LocalAlloc(
				    LMEM_FIXED | LMEM_ZEROINIT,
				    sizeof(NAMEENTRY) * g_crdnSubject);

    if (NULL == pNameEntryAlloc)
    {
        hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    pNameTable->cnt = g_crdnSubject;
    pNameTable->pNameEntry = pNameEntryAlloc;

    szTest[0] = 'a';
    szTest[1] = '\0';

    for (i = 0; i < g_crdnSubject; i++)
    {
	pNameEntry = &pNameTable->pNameEntry[i];
        pNameEntry->pszObjId = g_ardnSubject[i].pszObjId;
        pNameEntry->BerTag = g_ardnSubject[i].BerTag;

        pbString = (BYTE *) LocalAlloc(LMEM_FIXED, sizeof(szTest));
        if (NULL == pbString)
        {
            hr = E_OUTOFMEMORY;
	    _JumpError(hr, error, "LocalAlloc");
        }
        CopyMemory(pbString, szTest, sizeof(szTest));
        pNameEntry->cbData = sizeof(szTest) - 1;
        pNameEntry->pbData = pbString;
	if ('z' == szTest[0])
	{
	    szTest[0] = 'a';
	}
	else
	{
	    szTest[0]++;
	}
    }
    pNameEntryAlloc = NULL;
    hr = S_OK;

error:
    if (NULL != pNameEntryAlloc)
    {
	FreeLocalMemory(pNameTable);
    }
    return(hr);
}


BOOL
PreparePrivateKeyForImport(
    IN BYTE *pbBlob,
    IN DWORD cbBlob,
    OUT BSAFE_PRV_KEY *pPriKey,
    IN OUT DWORD *pcbPriKey,
    OUT BSAFE_PUB_KEY *pPubKey,
    IN OUT DWORD *pcbPubKey)
{
    EXPORT_PRV_KEY *pExportKey = (EXPORT_PRV_KEY *) pbBlob;
    DWORD cbHalfModLen;
    DWORD cbPub;
    DWORD cbPri;
    BYTE *pbIn;
    BYTE *pbOut;

    if (RSA2 != pExportKey->magic)
    {
        return(FALSE);
    }
    cbHalfModLen = pExportKey->bitlen / 16;

    cbPub = sizeof(BSAFE_PUB_KEY) + (cbHalfModLen + sizeof(DWORD)) * 2;
    cbPri = sizeof(BSAFE_PRV_KEY) + (cbHalfModLen + sizeof(DWORD)) * 10;
    if (NULL == pPriKey || NULL == pPubKey)
    {
        *pcbPubKey = cbPub;
        *pcbPriKey = cbPri;
        return(TRUE);
    }

    if (*pcbPubKey < cbPub || *pcbPriKey < cbPri)
    {
        *pcbPubKey = cbPub;
        *pcbPriKey = cbPri;
        return(FALSE);
    }
    else
    {
        // form the public key
        ZeroMemory(pPubKey, *pcbPubKey);
        pPubKey->magic = RSA1;
        pPubKey->keylen = (cbHalfModLen + sizeof(DWORD)) * 2;
        pPubKey->bitlen = pExportKey->bitlen;
        pPubKey->datalen = cbHalfModLen * 2 - 1;
        pPubKey->pubexp = pExportKey->pubexp;

        pbIn = pbBlob + sizeof(EXPORT_PRV_KEY);
        pbOut = (BYTE *) pPubKey + sizeof(BSAFE_PUB_KEY);

        CopyMemory(pbOut, pbIn, cbHalfModLen * 2);

        // form the private key
        ZeroMemory(pPriKey, *pcbPriKey);
        pPriKey->magic = pExportKey->magic;
        pPriKey->keylen = (cbHalfModLen + sizeof(DWORD)) * 2;
        pPriKey->bitlen = pExportKey->bitlen;
        pPriKey->datalen = cbHalfModLen * 2 - 1;
        pPriKey->pubexp = pExportKey->pubexp;

        pbOut = (BYTE *) pPriKey + sizeof(BSAFE_PRV_KEY);

        CopyMemory(pbOut, pbIn, cbHalfModLen * 2);

        pbOut += (cbHalfModLen + sizeof(DWORD)) * 2;
        pbIn += cbHalfModLen * 2;
        CopyMemory(pbOut, pbIn, cbHalfModLen);

        pbOut += cbHalfModLen + sizeof(DWORD);
        pbIn += cbHalfModLen;
        CopyMemory(pbOut, pbIn, cbHalfModLen);

        pbOut += cbHalfModLen + sizeof(DWORD);
        pbIn += cbHalfModLen;
        CopyMemory(pbOut, pbIn, cbHalfModLen);

        pbOut += cbHalfModLen + sizeof(DWORD);
        pbIn += cbHalfModLen;
        CopyMemory(pbOut, pbIn, cbHalfModLen);

        pbOut += cbHalfModLen + sizeof(DWORD);
        pbIn += cbHalfModLen;
        CopyMemory(pbOut, pbIn, cbHalfModLen);

        pbOut += cbHalfModLen + sizeof(DWORD);
        pbIn += cbHalfModLen;
        CopyMemory(pbOut, pbIn, cbHalfModLen * 2);
    }
    *pcbPubKey = cbPub;
    *pcbPriKey = cbPri;
    return(TRUE);
}


HRESULT
GetPrivateKeyStuff(
    PctPrivateKey **ppKey)
{
    HRESULT hr;
    BYTE *pbData;
    PctPrivateKey *pKey = NULL;
    HCRYPTKEY hKey = NULL;

    if (!CryptAcquireContext(
			&g_hMe,
			g_wszTestKey,
			MS_DEF_PROV,
			PROV_RSA_FULL,
			CRYPT_DELETEKEYSET))
    {
	hr = myHLastError();
	_PrintError(hr, "CryptAcquireContext");
    }

    if (!CryptAcquireContext(
			&g_hMe,
			g_wszTestKey,
			MS_DEF_PROV,
			PROV_RSA_FULL,
			CRYPT_NEWKEYSET))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptAcquireContext");
    }

    if (!CryptGetUserKey(g_hMe, AT_SIGNATURE, &hKey))
    {
	hr = myHLastError();
	_PrintError2(hr, "CryptGetUserKey", hr);

        if (!CryptGenKey(g_hMe, AT_SIGNATURE, CRYPT_EXPORTABLE, &hKey))
        {
            hr = myHLastError();
	    _JumpError(hr, error, "CryptGenKey");
        }
    }

    g_cbPrivateKey = sizeof(g_CAPIPrivateKey);

    if (!CryptExportKey(
		    hKey,
		    0,
		    PRIVATEKEYBLOB,
		    0L,
		    &g_CAPIPrivateKey[0],
		    &g_cbPrivateKey))
    {
        hr = myHLastError();
	_JumpError(hr, error, "CryptExportKey");
    }

    pbData = &g_CAPIPrivateKey[sizeof(BLOBHEADER)];

    if (!PreparePrivateKeyForImport(
				pbData,
				g_cbPrivateKey - sizeof(BLOBHEADER),
				NULL,
				&g_cbRSAPrivateKey,
				NULL,
				&g_cbRSAPublicKey))
    {
        hr = NTE_BAD_KEY;
	_JumpError(hr, error, "PreparePrivateKeyForImport");
    }

    pKey = (PctPrivateKey *) LocalAlloc(
				LMEM_FIXED,
				g_cbRSAPrivateKey + sizeof(PctPrivateKey));

    g_pRSAPublicKey = (BSAFE_PUB_KEY *) LocalAlloc(
					    LMEM_FIXED,
					    g_cbRSAPublicKey);

    if (pKey == NULL || g_pRSAPublicKey == NULL)
    {
        hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    pKey->cbKey = g_cbRSAPrivateKey;
    if (!PreparePrivateKeyForImport(
				pbData,
				g_cbPrivateKey - sizeof(BLOBHEADER),
				(BSAFE_PRV_KEY *) pKey->pKey,
				&g_cbRSAPrivateKey,
				g_pRSAPublicKey,
				&g_cbRSAPublicKey))
    {
        hr = NTE_BAD_KEY;
	_JumpError(hr, error, "PreparePrivateKeyForImport");
    }
    hr = S_OK;

error:
    if (NULL != hKey)
    {
        CryptDestroyKey(hKey);
    }
    *ppKey = pKey;
    return(hr);
}


VOID
ReverseMemCopy(
    OUT BYTE *pbDest,
    IN BYTE const *pbSource,
    IN DWORD cb)
{
    BYTE *pb;

    pb = pbDest + cb - 1;
    do
    {
        *pb-- = *pbSource++;
    } while (pb >= pbDest);
}


BOOL WINAPI
SigRSAMD5Sign(
    IN BYTE *pbData,
    IN DWORD cbData,
    OUT BYTE *pbSigned,
    OUT DWORD *pcbSigned,
    IN PctPrivateKey const *pKey)
{
    MD5_CTX DigCtx;
    BSAFE_PRV_KEY *pk = (BSAFE_PRV_KEY *) pKey->pKey;
    BYTE LocalBuffer[300];
    BYTE LocalOutput[300];
    DWORD cb;

    //DumpHex(pbData, cbData);
    if (pk->datalen > sizeof(LocalBuffer)) 
    {
        return(FALSE);
    }

    // Generate the checksum
    MD5Init(&DigCtx);
    MD5Update(&DigCtx, pbData, cbData);
    MD5Final(&DigCtx);

    FillMemory(LocalBuffer, pk->keylen, 0);

    ReverseMemCopy(LocalBuffer, DigCtx.digest, 16);
    ReverseMemCopy(LocalBuffer + 16, MD5_PRELUDE, sizeof(MD5_PRELUDE));
    cb = sizeof(MD5_PRELUDE) + 16;
    LocalBuffer[cb++] = 0;
    while (cb < pk->datalen - 1)
    {
        LocalBuffer[cb++] = 0xff;
    }

    // Make into pkcs block type 1
    LocalBuffer[pk->datalen - 1] = 1;

    *pcbSigned = pk->datalen + 1;

    if (!BSafeDecPrivate(pk, LocalBuffer, LocalOutput))
    {
        return(FALSE);
    }
    ReverseMemCopy(pbSigned, LocalOutput,  *pcbSigned);
    //DumpHex(pbSigned, *pcbSigned);
    return(TRUE);
}


long
EncodeSubjectPubKeyInfo(
    IN PctPrivateKey const *pKey,
    OUT BYTE *pbBuffer)
{
    BYTE *pbEncoded;
    LONG cbResult;
    LONG cbResultHeader;
    LONG PkResult;
    LONG PkResultHeader;
    BYTE *pbSave;
    BYTE *pbBitString;
    BYTE *pbBitStringBase;
    BYTE *pbTop;
    DWORD EstimatedLength;
    BSAFE_PRV_KEY *pk = (BSAFE_PRV_KEY *) pKey->pKey;

    // Encode public key now...

    EstimatedLength = pk->datalen + 32;

    pbEncoded = pbBuffer;

    cbResultHeader = EncodeHeader(pbEncoded, EstimatedLength);
    pbEncoded += cbResultHeader;

    pbTop = pbEncoded;

    cbResult = EncodeAlgorithm(pbEncoded, ALGTYPE_KEYEXCH_RSA_MD5);
    if (0 > cbResult)
    {
        return(-1);
    }
    pbEncoded += cbResult;

    // now, serialize the rsa key data:

    pbBitString = (BYTE *) LocalAlloc(LMEM_FIXED, EstimatedLength);
    if (NULL == pbBitString)
    {
        return(-1);
    }
    pbBitStringBase = pbBitString;

    // Encode the Sequence header, public key base and exponent as integers

    PkResultHeader = EncodeHeader(pbBitString, EstimatedLength);
    pbBitString += PkResultHeader;

    pbSave = pbBitString;

    PkResult = EncodeInteger(pbBitString, (BYTE *) (pk + 1), pk->keylen);
    pbBitString += PkResult;

    PkResult = EncodeInteger(pbBitString, (BYTE *) &pk->pubexp, sizeof(DWORD));
    pbBitString += PkResult;

    // Rewrite the bitstring header with an accurate length.

    PkResult = EncodeHeader(
			pbBitStringBase,
			SAFE_SUBTRACT_POINTERS(pbBitString, pbSave));

    // Encode the public key sequence as a raw bitstring, and free the memory.

    cbResult = EncodeBitString(
			pbEncoded,
			pbBitStringBase,
			SAFE_SUBTRACT_POINTERS(pbBitString, pbBitStringBase));
    pbEncoded += cbResult;

    LocalFree(pbBitStringBase);

    // Rewrite the header with an accurate length.

    cbResult = EncodeHeader(pbBuffer, SAFE_SUBTRACT_POINTERS(pbEncoded, pbTop));

    return(cbResult + SAFE_SUBTRACT_POINTERS(pbEncoded, pbTop));
}


#if 0
a0 <len>		BER_OPTIONAL | 0 -- Request Attributes
    30 <len>		BER_SEQUENCE
	06 <len>	BER_OBJECT_ID -- szOID_CERT_EXTENSIONS
	31 <len>	BER_SET
	    30 <len>	BER_SEQUENCE
		30 <len>	BER_SEQUENCE	(extension[0])
		    06 <len> BER_OBJECT_ID
		    01 <len> BER_BOOL (Optional)
		    04 <len> BER_OCTET_STRING

		30 <len>	BER_SEQUENCE	(extension[1])
		    06 <len> BER_OBJECT_ID
		    04 <len> BER_OCTET_STRING

		30 <len>	BER_SEQUENCE	(extension[2])
		    06 <len> BER_OBJECT_ID
		    04 <len> BER_OCTET_STRING
#endif


long
AllocEncodeUnicodeString(
    IN WCHAR const *pwszCertType,
    OUT BYTE **ppbOut)
{
    BYTE *pb = NULL;
    LONG cb;

    cb = EncodeUnicodeString(NULL, pwszCertType);
    pb = (BYTE *) LocalAlloc(LMEM_FIXED, cb);
    if (NULL == pb)
    {
	cb = -1;
	goto error;
    }
    *ppbOut = pb;
    EncodeUnicodeString(pb, pwszCertType);

error:
    return(cb);
}


long
AllocEncodeExtensionArray(
    IN DWORD cExt,
    IN CERT_EXTENSION const *aExt,
    OUT BYTE **ppbExtensions)
{
    BYTE *pb;
    DWORD i;
    LONG cb;
    LONG cbExtTotal;
    LONG acbLen[3];
    LONG *acbExt = NULL;

    *ppbExtensions = NULL;

    acbExt = (LONG *) LocalAlloc(LMEM_FIXED, cExt * sizeof(acbExt[0]));
    if (NULL == acbExt)
    {
	cbExtTotal = -1;
	_JumpError(-1, error, "LocalAlloc");
    }

    // Construct size from the bottom up.

    cbExtTotal = 0;
    for (i = 0; i < cExt; i++)
    {
	// BER_OBJECT_ID: Extension OID

	cb = EncodeObjId(NULL, aExt[i].pszObjId);
	if (-1 == cb)
	{
	    _JumpError(-1, error, "EncodeObjId");
	}
	acbExt[i] = cb;

	if (aExt[i].fCritical)
	{
	    // BER_BOOL: fCritical

	    acbExt[i] += 1 + EncodeLength(NULL, 1);
	    acbExt[i]++;			// boolean value
	}

	// BER_OCTET_STRING: Extension octet string value
	
	acbExt[i] += 1 + EncodeLength(NULL, aExt[i].Value.cbData);
	acbExt[i] += aExt[i].Value.cbData;	// octet string

	// BER_SEQUENCE: Extension Sequence

	cbExtTotal += 1 + EncodeLength(NULL, acbExt[i]);
	cbExtTotal += acbExt[i];
    }

    // BER_SEQUENCE: Extension Array Sequence

    acbLen[2] = cbExtTotal;
    cbExtTotal += 1 + EncodeLength(NULL, cbExtTotal);

    // BER_SET: Attribute Value

    acbLen[1] = cbExtTotal;
    cbExtTotal += 1 + EncodeLength(NULL, cbExtTotal);

    // BER_OBJECT_ID: Attribute OID

    cb = EncodeObjId(NULL, szOID_CERT_EXTENSIONS);
    if (-1 == cb)
    {
	_JumpError(-1, error, "EncodeObjId");
    }
    cbExtTotal += cb;

    // BER_SEQUENCE: Attribute Array Sequence

    acbLen[0] = cbExtTotal;
    cbExtTotal += 1 + EncodeLength(NULL, cbExtTotal);

    // Allocate memory and encode the extensions

    pb = (BYTE *) LocalAlloc(LMEM_FIXED, cbExtTotal);
    if (NULL == pb)
    {
	cbExtTotal = -1;
	_JumpError(-1, error, "LocalAlloc");
    }
    *ppbExtensions = pb;

    *pb++ = BER_SEQUENCE;		// Attribute Array Sequence
    pb += EncodeLength(pb, acbLen[0]);

    pb += EncodeObjId(pb, szOID_CERT_EXTENSIONS);

    *pb++ = BER_SET;			// Attribute Value
    pb += EncodeLength(pb, acbLen[1]);

    *pb++ = BER_SEQUENCE;		// Extension Array Sequence
    pb += EncodeLength(pb, acbLen[2]);

    CSASSERT(*ppbExtensions + cbExtTotal >= pb);

    for (i = 0; i < cExt; i++)
    {
	CSASSERT(*ppbExtensions + cbExtTotal > pb);

	*pb++ = BER_SEQUENCE;		// Extension Sequence
	pb += EncodeLength(pb, acbExt[i]);

	// BER_OBJECT_ID: Extension OID

	pb += EncodeObjId(pb, aExt[i].pszObjId);

	if (aExt[i].fCritical)
	{
	    *pb++ = BER_BOOL;		// fCritical
	    pb += EncodeLength(pb, 1);
	    *pb++ = 0xff;
	}

	*pb++ = BER_OCTET_STRING;	// Extension octet string value
	pb += EncodeLength(pb, aExt[i].Value.cbData);

	CopyMemory(pb, aExt[i].Value.pbData, aExt[i].Value.cbData);
	pb += aExt[i].Value.cbData;
    }
    CSASSERT(*ppbExtensions + cbExtTotal == pb);

error:
    if (NULL != acbExt)
    {
	LocalFree(acbExt);
    }
    return(cbExtTotal);
}


long
EncodeExtensions(
    IN WCHAR const *pwszCertType,
    OUT BYTE **ppbExtensions)
{
    LONG cbExt;
    BYTE *pbExt = NULL;
    CERT_EXTENSION aExt[1];
    DWORD cExt = 0;
    DWORD i;

    // Allocate memory and construct the CertType extension:

    aExt[cExt].pszObjId = szOID_ENROLL_CERTTYPE_EXTENSION;
    aExt[cExt].fCritical = FALSE;
    aExt[cExt].Value.cbData = AllocEncodeUnicodeString(
						pwszCertType,
						&aExt[cExt].Value.pbData);
    //DumpHex(aExt[cExt].Value.pbData, aExt[cExt].Value.cbData);
    cExt++;


    cbExt = AllocEncodeExtensionArray(cExt, aExt, ppbExtensions);
    if (-1 == cbExt)
    {
	_JumpError(-1, error, "AllocEncodeExtensionArray");
    }

error:
    for (i = 0; i < cExt; i++)
    {
	if (NULL != aExt[i].Value.pbData)
	{
	    LocalFree(aExt[i].Value.pbData);
	}
    }
    return(cbExt);
}


HRESULT
EncodeRequest(
    IN PctPrivateKey const *pKey,
    IN NAMETABLE const *pNameTable,
    OUT BYTE **ppbRequest,
    OUT DWORD *pcbRequest)
{
    HRESULT hr;
    BYTE *pbRequest0Alloc = NULL;
    BYTE *pbRequest1Alloc = NULL;
    BYTE *pbSigAlloc = NULL;

    BYTE *pbRequest0;

    BYTE *pbSave;
    BYTE *pbEncoded;

    BYTE *pbExt;
    LONG cbExt;

    LONG cbResult;
    LONG cbEncoded;
    BYTE bZero;
    LONG cbDN;
    DWORD cbRequest0;
    DWORD cbRequest1;
    LONG cbLenRequest;
    BSAFE_PRV_KEY *pk = (BSAFE_PRV_KEY *) pKey->pKey;

    cbExt = EncodeExtensions(L"User", &pbExt);
    if (-1 == cbExt)
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	_JumpError(hr, error, "EncodeExtensions");
    }
    //DumpHex(pbExt, cbExt);

    cbDN = EncodeDN(NULL, pNameTable);
    if (-1 == cbDN)
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	_JumpError(hr, error, "EncodeDN");
    }
    cbRequest0 = pk->datalen + 32 + cbDN + 16 + cbExt + 3;
    pbRequest0Alloc = (BYTE *) LocalAlloc(LMEM_FIXED, cbRequest0);
    if (NULL == pbRequest0Alloc)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    pbRequest0 = pbRequest0Alloc;
    pbEncoded = pbRequest0;

    // Encode BER_SEQUENCE: Version+Subject+Key+Attributes Sequence

    cbLenRequest = EncodeHeader(pbEncoded, cbRequest0);
    pbEncoded += cbLenRequest;

    pbSave = pbEncoded;		// Save pointer past sequence length

    // Encode integer 0: Version 1 PKCS10

    bZero = (BYTE) CERT_REQUEST_V1;
    pbEncoded += EncodeInteger(pbEncoded, &bZero, sizeof(bZero));

    // Encode sequence of names

    cbResult = EncodeDN(pbEncoded, pNameTable);
    if (0 > cbResult)
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	_JumpError(hr, error, "EncodeDN");
    }
    pbEncoded += cbResult;

    cbResult = EncodeSubjectPubKeyInfo(pKey, pbEncoded);
    if (0 > cbResult)
    {
	hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	_JumpError(hr, error, "EncodeSubjectPubKeyInfo");
    }
    pbEncoded += cbResult;

    // Encode attributes:
    // BER_OPTIONAL | 0: Attribute Field

    cbResult = EncodeAttributeHeader(pbEncoded, cbExt);
    pbEncoded += cbResult;
    CopyMemory(pbEncoded, pbExt, cbExt);
    pbEncoded += cbExt;

    // Encode BER_SEQUENCE: Version+Subject+Key+Attributes Sequence (again)

    cbEncoded = SAFE_SUBTRACT_POINTERS(pbEncoded, pbSave);
    cbResult = EncodeHeader(pbRequest0, cbEncoded);

    // If the header sequence length takes up less space than we anticipated,
    // add the difference to the base pointer and encode the header again,
    // right before the encoded data.

    if (cbResult != cbLenRequest)
    {
        CSASSERT(cbResult < cbLenRequest);
	pbRequest0 += cbLenRequest - cbResult;

	// Encode BER_SEQUENCE: Version+Subject+Key+Attributes Sequence (again)

	cbResult = EncodeHeader(pbRequest0, cbEncoded);
    }

    cbRequest0 = cbResult + SAFE_SUBTRACT_POINTERS(pbEncoded, pbSave);
    //DumpHex(pbRequest0, cbRequest0);

    // How much space do we need?

    cbRequest1 = cbRequest0 + pk->datalen + 32;
    pbRequest1Alloc = (BYTE *) LocalAlloc(LMEM_FIXED, cbRequest1);
    if (NULL == pbRequest1Alloc)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    pbEncoded = pbRequest1Alloc;

    // Encode BER_SEQUENCE: outer Request Sequence

    cbLenRequest = EncodeHeader(pbEncoded, cbRequest1);
    pbEncoded += cbLenRequest;

    pbSave = pbEncoded;		// Save pointer past outer sequence length

    CopyMemory(pbEncoded, pbRequest0, cbRequest0);

    pbEncoded += cbRequest0;

    cbResult = EncodeAlgorithm(pbEncoded, ALGTYPE_SIG_RSA_MD5);
    pbEncoded += cbResult;

    //DumpHex(pbRequest1Alloc, cbRequest1);

    cbResult = pk->datalen + 16;
    pbSigAlloc = (BYTE *) LocalAlloc(LMEM_FIXED, cbResult);
    if (NULL == pbSigAlloc)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    if (!SigRSAMD5Sign(
	    pbRequest0,
	    cbRequest0,
	    pbSigAlloc,
	    (DWORD *) &cbResult,
	    pKey))
    {
	hr = E_FAIL;
	_JumpError(hr, error, "SigRSAMD5Sign");
    }

    pbEncoded += EncodeBitString(pbEncoded, pbSigAlloc, cbResult);

    cbEncoded = SAFE_SUBTRACT_POINTERS(pbEncoded, pbSave);
    cbResult = EncodeHeader(pbRequest1Alloc, cbEncoded);
    cbRequest1 = cbResult + cbEncoded;

    if (cbResult != cbLenRequest)
    {
        if (cbResult > cbLenRequest)
        {
	    // The chunk has actually grown from the estimate.

            BYTE *pbT;
	    
            pbT = (BYTE *) LocalAlloc(LMEM_FIXED, cbRequest1);
            if (NULL == pbT)
	    {
		hr = E_OUTOFMEMORY;
		_JumpError(hr, error, "LocalAlloc");
	    }

	    EncodeHeader(pbT, cbEncoded);
	    CopyMemory(
		    pbT + cbResult,
		    pbSave,
		    cbEncoded);

	    LocalFree(pbRequest1Alloc);
	    pbRequest1Alloc = pbT;
        }
        else
        {
	    cbResult = EncodeHeader(pbRequest1Alloc, cbEncoded);
            MoveMemory(
		pbRequest1Alloc + cbResult,
		pbRequest1Alloc + cbLenRequest,
		cbEncoded);
        }
    }
    *ppbRequest = pbRequest1Alloc;
    *pcbRequest = cbRequest1;
    pbRequest1Alloc = NULL;
    //DumpHex(*ppbRequest, *pcbRequest);
    hr = S_OK;

error:
    if (NULL != pbSigAlloc)
    {
	LocalFree(pbSigAlloc);
    }
    if (NULL != pbRequest1Alloc)
    {
	LocalFree(pbRequest1Alloc);
    }
    if (NULL != pbRequest0Alloc)
    {
	LocalFree(pbRequest0Alloc);
    }
    if (NULL != pbExt)
    {
	LocalFree(pbExt);
    }
    return(hr);
}


HRESULT
GetAndCompareProperty(
    CERT_NAME_INFO *pNameInfo,
    char const *pszObjId,
    char const *pszValue,
    BYTE **pbProp,
    DWORD *pcbProp,
    BOOL *pfMatch)
{
    HRESULT hr;
    CERT_RDN_ATTR *prdnaT;
    CERT_RDN *prdn;
    CERT_RDN *prdnEnd;

    *pfMatch = FALSE;
    prdnaT = NULL;
    for (
	prdn = pNameInfo->rgRDN, prdnEnd = &prdn[pNameInfo->cRDN];
	prdn < prdnEnd;
	prdn++)
    {
	CERT_RDN_ATTR *prdna;
	CERT_RDN_ATTR *prdnaEnd;

	for (
	    prdna = prdn->rgRDNAttr, prdnaEnd = &prdna[prdn->cRDNAttr];
	    prdna < prdnaEnd;
	    prdna++)
	{
	    if (0 == strcmp(prdna->pszObjId, pszObjId))
	    {
		prdnaT = prdna;

		if (prdnaT->Value.cbData == strlen(pszValue) &&
		    0 == memcmp(pszValue, prdnaT->Value.pbData, prdnaT->Value.cbData))
		{
		    *pfMatch = TRUE;
		}
		else if (g_fAllowDups)
		{
		    continue;
		}
		prdn = prdnEnd;	// exit outer for loop, too.
		break;
	    }
	}
    }
    if (NULL == prdnaT)
    {
	hr = CERTSRV_E_PROPERTY_EMPTY;
	_JumpError2(hr, error, "Missing Property", CERTSRV_E_PROPERTY_EMPTY);
    }
    *pbProp = prdnaT->Value.pbData;
    *pcbProp = prdnaT->Value.cbData;
    hr = S_OK;

error:
    return(hr);

}


HRESULT
CheckProperties(
    DWORD ReqId,
    NAMETABLE *pNameTable,
    DWORD CertNumber,
    CERT_NAME_INFO *pNameInfo)
{
    HRESULT hr;
    BYTE *pbProp;
    DWORD cbProp;
    DWORD i;
    DWORD dwCount = 0;
    BOOL fMatch;

    if (g_fPrintProperties)
    {
        wprintf(
	    L"Properties for Certificate %u, RequestId %u:\n",
	    CertNumber,
	    ReqId);
    }

    for (i = 0; i < pNameTable->cnt; i++)
    {
	NAMEENTRY *pNameEntry;
	RDNENTRY *prdn;

	pNameEntry = &pNameTable->pNameEntry[i];
	prdn = &g_ardnSubject[pNameEntry->iRDN];

        hr = GetAndCompareProperty(
		    pNameInfo,
		    prdn->pszObjId,
		    (char const *) pNameEntry->pbData,
		    &pbProp,
		    &cbProp,
		    &fMatch);

        if (CERTSRV_E_PROPERTY_EMPTY == hr)
        {
	    //_PrintError(hr, "GetAndCompareProperty");
	    pbProp = NULL;
            hr = S_OK;
        }
	_JumpIfError(hr, error, "GetAndCompareProperty");

        if (NULL != pbProp && !fMatch)
        {
	    wprintf(
		L"Property doesn't match: Expected %hs=\"%hs\", pbProp = \"%hs\"\n",
		prdn->pszShortName,
		pNameEntry->pbData,
		pbProp);

            hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "GetAndCompareProperty: no match");
        }

        if (g_fPrintProperties)
        {
	    DWORD ccol;

#define CCOL_OID 10
	    ccol = strlen(prdn->pszObjId) + 1;
	    if (ccol < CCOL_OID)
	    {
		ccol = CCOL_OID - ccol;
	    }
	    else
	    {
		ccol = 0;
	    }
            wprintf(
		L"  %u: %hs: %*s%hs=%hs%hs%hs\n",
		i,
		prdn->pszObjId,
		ccol,
		"",
		prdn->pszShortName,
		NULL == pbProp? "" : "\"",
		NULL == pbProp? " -- MISSING --" : (char const *) pbProp,
		NULL == pbProp? "" : "\"");
        }
    }
    if (g_fPrintProperties)
    {
        wprintf(L"\n");
    }
    hr = S_OK;

error:
    return(hr);

}


HRESULT
EncodeRenewal(
    IN BYTE const *pbRequest,
    IN DWORD cbRequest,
    OUT BYTE **ppbRenewal,
    OUT DWORD *pcbRenewal)
{
    HRESULT hr;

    *ppbRenewal = (BYTE *) LocalAlloc(LMEM_FIXED, cbRequest);
    if (NULL == *ppbRenewal)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    CopyMemory(*ppbRenewal, pbRequest, cbRequest);
    *pcbRenewal = cbRequest;
    hr = S_OK;

error:
    return(hr);
}


HRESULT
SubmitRequest(
    OPTIONAL IN DISPATCHINTERFACE *pdiRequest,
    IN DWORD Flags,
    IN BYTE const *pbRequest,
    IN DWORD cbRequest,
    IN WCHAR const *pwszAttributes,
    IN WCHAR const *pwszConfig,
    OUT DWORD *pRequestIdOut,
    OUT DWORD *pDisposition,
    OUT HRESULT *phrLastStatus,
    OUT WCHAR **ppwszDisposition,
    OUT BYTE **ppbCert,
    OUT DWORD *pcbCert)
{
    HRESULT hr;
    WCHAR *pwszRequest = NULL;
    BSTR strCert = NULL;
    BSTR strCertChain = NULL;
    BSTR strDisposition = NULL;
    BYTE *pbCert = NULL;
    DWORD cbCert;
    BYTE const *pbChain;
    DWORD cbChain;
    CERTSERVERENROLL *pcsEnroll = NULL;
    WCHAR *pwszServer = NULL;
    WCHAR *pwszAuthority = NULL;
    WCHAR *pwszDisposition;

    *phrLastStatus = S_OK;
    *ppwszDisposition = NULL;
    *ppbCert = NULL;
    *pRequestIdOut = 0;

    if (NULL == pdiRequest)
    {
	hr = mySplitConfigString(pwszConfig, &pwszServer, &pwszAuthority);
	_JumpIfError(hr, error, "mySplitConfigString");

	// CertServerSubmitRequest can only handle binary requests;
	// pass the request in binary form, and pass Flags to so indicate.

	hr = CertServerSubmitRequest(
				CR_IN_BINARY | Flags,
				pbRequest,
				cbRequest,
				pwszAttributes,
				pwszServer,
				pwszAuthority,
				&pcsEnroll);
	_JumpIfError(hr, error, "CertServerSubmitRequest");

	*phrLastStatus = pcsEnroll->hrLastStatus;
	_PrintIfError2(
		*phrLastStatus,
		"pcsEnroll->hrLastStatus Real Status",
		HRESULT_FROM_WIN32(ERROR_INVALID_DATA));

	pwszDisposition = pcsEnroll->pwszDispositionMessage;
	*pDisposition = pcsEnroll->Disposition;
	*pRequestIdOut = pcsEnroll->RequestId;
    }
    else
    {
	hr = myCryptBinaryToString(
			    pbRequest,
			    cbRequest,
			    CRYPT_STRING_BASE64REQUESTHEADER,
			    &pwszRequest);
	_JumpIfError(hr, error, "myCryptBinaryToString");

	if (g_fRPC)
	{
	    Flags |= CR_IN_RPC;
	}
	hr = Request_Submit(
			pdiRequest,
			CR_IN_BASE64HEADER | Flags,
			pwszRequest,
			sizeof(WCHAR) * wcslen(pwszRequest),
			pwszAttributes,
			pwszConfig,
			(LONG *) pDisposition);
	if (S_OK != hr)
	{
	    _PrintError2(
		    hr,
		    "Request_Submit",
		    HRESULT_FROM_WIN32(ERROR_INVALID_DATA));

	    // Collect the RequestId for potential error reporting:
	    Request_GetRequestId(pdiRequest, (LONG *) pRequestIdOut);

	    hr = Request_GetLastStatus(pdiRequest, phrLastStatus);
	    _JumpIfError(hr, error, "Request_GetLastStatus");

	    if (FAILED(*phrLastStatus))
	    {
		hr = *phrLastStatus;
	    }
	    _JumpError2(
		    hr,
		    error,
		    "Request_GetLastStatus Real Status",
		    HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
	}
	hr = Request_GetLastStatus(pdiRequest, phrLastStatus);
	_JumpIfError(hr, error, "Request_GetLastStatus");

	_PrintIfError(*phrLastStatus, "Request_GetLastStatus Real Status");

	hr = Request_GetDispositionMessage(pdiRequest, &strDisposition);
	_JumpIfError(hr, error, "Request_GetDispositionMessage");

	hr = Request_GetRequestId(pdiRequest, (LONG *) pRequestIdOut);
	_JumpIfError(hr, error, "Request_GetrequestId");

	pwszDisposition = strDisposition;
    }

    if (CR_DISP_ISSUED == *pDisposition)
    {
	if (NULL == pdiRequest)
	{
	    cbCert = pcsEnroll->cbCert;
	    pbCert = (BYTE *) LocalAlloc(LMEM_FIXED, cbCert);
	    if (NULL == pbCert)
	    {
		hr = E_OUTOFMEMORY;
		_JumpError(hr, error, "LocalAlloc");
	    }
	    CopyMemory(pbCert, pcsEnroll->pbCert, cbCert);
	}
	else
	{
	    hr = Request_GetCertificate(
				    pdiRequest,
				    CR_OUT_BASE64HEADER,
				    &strCert);
	    _JumpIfError(hr, error, "Request_GetCertificate");

	    hr = myCryptStringToBinary(
				strCert,
				wcslen(strCert),
				CRYPT_STRING_BASE64HEADER,
				&pbCert,
				&cbCert,
				NULL,
				NULL);
	    _JumpIfError(hr, error, "myCryptStringToBinary");
	}

	if (g_fSave)
	{
	    hr = EncodeToFileW(
			L"test.crt",
			pbCert,
			cbCert,
			DECF_FORCEOVERWRITE | CRYPT_STRING_BINARY);
	    _JumpIfError(hr, error, "EncodeToFileW");

	    if (NULL == pdiRequest)
	    {
		pbChain = pcsEnroll->pbCertChain;
		cbChain = pcsEnroll->cbCertChain;
	    }
	    else
	    {
		hr = Request_GetCertificate(
					pdiRequest,
					CR_OUT_BINARY | CR_OUT_CHAIN,
					&strCertChain);
		_JumpIfError(hr, error, "Request_GetCertificate");

		pbChain = (BYTE const *) strCertChain;
		cbChain = SysStringByteLen(strCertChain);
	    }
	    hr = EncodeToFileW(
			L"testchain.crt",
			pbChain,
			cbChain,
			DECF_FORCEOVERWRITE | CRYPT_STRING_BINARY);
	    _JumpIfError(hr, error, "EncodeToFileW");
	}
    }

    if (NULL != pwszDisposition)
    {
	*ppwszDisposition = (WCHAR *) LocalAlloc(
				LMEM_FIXED,
				(wcslen(pwszDisposition) + 1) * sizeof(WCHAR));
	if (NULL == *ppwszDisposition)
	{
	    hr = E_OUTOFMEMORY;
	    _JumpError(hr, error, "LocalAlloc");
	}
	wcscpy(*ppwszDisposition, pwszDisposition);
    }
    *pcbCert = cbCert;
    *ppbCert = pbCert;
    pbCert = NULL;
    hr = S_OK;

error:
    if (NULL != pwszServer)
    {
	LocalFree(pwszServer);
    }
    if (NULL != pwszAuthority)
    {
	LocalFree(pwszAuthority);
    }
    if (NULL != pbCert)
    {
	LocalFree(pbCert);
    }
    if (NULL != pcsEnroll)
    {
	CertServerFreeMemory(pcsEnroll);
    }
    if (NULL != strCertChain)
    {
	SysFreeString(strCertChain);
    }
    if (NULL != strCert)
    {
	SysFreeString(strCert);
    }
    if (NULL != strDisposition)
    {
	SysFreeString(strDisposition);
    }
    if (NULL != pwszRequest)
    {
	LocalFree(pwszRequest);
    }
    return(hr);
}


HRESULT
TestOneRequest(
    IN PctPrivateKey const *pKey,
    OPTIONAL IN DISPATCHINTERFACE *pdiRequest,
    IN WCHAR const *pwszConfig,
    IN DWORD CertNumber,
    OUT DWORD *pRequestId,
    OUT DWORD *pTimeOneRequest)
{
    HRESULT hr;
    HRESULT hrLastStatus;
    NAMETABLE NameTable;
    BOOL fTableAllocated;
    BYTE *pbRequest;
    DWORD cbRequest;
    BYTE *pbCert;
    DWORD cbCert;
    CERT_CONTEXT const *pCertContext;
    DWORD RequestIdOut = 0;
    DWORD Disposition;
    WCHAR *pwszDisposition = NULL;
    CERT_NAME_INFO *pNameInfo;
    DWORD cbNameInfo;
    CERT_INFO const *pCertInfo;
    LONG Flags;
    WCHAR wszAttributes[MAX_PATH];

    fTableAllocated = FALSE;
    pbRequest = NULL;
    pbCert = NULL;
    pCertContext = NULL;
    pNameInfo = NULL;

    if (g_fDebug)
    {
	hr = GenerateTestNameTable(&NameTable);
	_JumpIfError(hr, error, "GenerateTestNameTable");
    }
    else
    {
	hr = GenerateNameTable(&NameTable);
	_JumpIfError(hr, error, "GenerateNameTable");
    }

    fTableAllocated = TRUE;

    hr = EncodeRequest(pKey, &NameTable, &pbRequest, &cbRequest);
    _JumpIfError(hr, error, "EncodeRequest");

    Flags = CR_IN_PKCS10;

    if (g_fRenewal)
    {
	BYTE *pbTmp;

	hr = EncodeRenewal(pbRequest, cbRequest, &pbTmp, &cbRequest);
	_JumpIfError(hr, error, "EncodeRenewal");

	LocalFree(pbRequest);
	pbRequest = pbTmp;
	Flags = CR_IN_PKCS7;
    }

    if (g_fSave)
    {
	hr = EncodeToFileW(
		    L"test.req",
		    pbRequest,
		    cbRequest,
		    DECF_FORCEOVERWRITE | CRYPT_STRING_BINARY);
	_JumpIfError(hr, error, "EncodeToFileW");
    }

    *pTimeOneRequest = 0 - GetTickCount();

    wsprintf(
	wszAttributes,
	L"\n"
	    L" attrib 1 end : value 1 end \t\r\n"
	    L"\tattrib 2 end:value_2_end\n"
	    L" \tattrib3:value-3-end\r\n"
	    L"Version:3\n"
	    L"RequestType:CertGen\n"
	    L"CertGenSequence:%u\n",
	CertNumber);

    hr = SubmitRequest(
		    pdiRequest,
		    Flags,
		    pbRequest,
		    cbRequest,
		    wszAttributes,
		    pwszConfig,
		    &RequestIdOut,
		    &Disposition,
		    &hrLastStatus,
		    &pwszDisposition,
		    &pbCert,
		    &cbCert);

    *pTimeOneRequest += GetTickCount();

    if (S_OK != hr)
    {
	_JumpError2(
		hr,
		error,
		"SubmitRequest",
		HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
    }
    if (CR_DISP_ISSUED != Disposition)
    {
	hr = hrLastStatus;
	if (S_OK == hr)
	{
	    hr = E_FAIL;
	}
	wprintf(
	    L"SubmitRequest disposition=%x hr=%x (%ws)\n",
	    Disposition,
	    hr,
	    NULL == pwszDisposition? L"???" : pwszDisposition);

	if (g_fIgnoreError)
	{
	    hr = S_OK;
	}
	if (CR_DISP_DENIED == Disposition && g_fIgnoreAccessDenied)
	{
	    hr = S_OK;
	}
	_JumpError(hr, error, "Cert not issued!");
    }

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

    pCertInfo = pCertContext->pCertInfo;

    if (!myDecodeName(
		    X509_ASN_ENCODING,
		    X509_NAME,
		    pCertInfo->Subject.pbData,
		    pCertInfo->Subject.cbData,
		    CERTLIB_USE_LOCALALLOC,
		    &pNameInfo,
		    &cbNameInfo))
    {
	hr = myHLastError();
	_JumpError(hr, error, "myDecodeName");
    }

    hr = CheckProperties(RequestIdOut, &NameTable, CertNumber, pNameInfo);
    _JumpIfError(hr, error, "CheckProperties");

error:
    *pRequestId = RequestIdOut;
    if (NULL != pwszDisposition)
    {
	LocalFree(pwszDisposition);
    }
    if (NULL != pNameInfo)
    {
	LocalFree(pNameInfo);
    }
    if (NULL != pCertContext)
    {
	CertFreeCertificateContext(pCertContext);
    }
    if (NULL != pbCert)
    {
	LocalFree(pbCert);
    }
    if (NULL != pbRequest)
    {
	LocalFree(pbRequest);
    }
    if (fTableAllocated)
    {
	FreeLocalMemory(&NameTable);
    }
    return(hr);
}


HRESULT
TestMain()
{
    HRESULT hr;
    PctPrivateKey *pKey = NULL;
    DWORD TimeStartTest;
    DWORD TimeStartLastN;
    DWORD TimeRequestTotal = 0;
    DWORD TimeRequestLastN = 0;
    DWORD TimeElapsedTotal;
    DWORD TotalCount = 0;
    DISPATCHINTERFACE diRequest;
    DISPATCHINTERFACE *pdiRequest = NULL;
    BOOL fCoInit = FALSE;
    DWORD RequestId = 0;
    WCHAR const *pwszConfig;
    BSTR strConfig = NULL;

    g_crdnMax = g_crdnSubject;
    hr = GetPrivateKeyStuff(&pKey);
    _JumpIfError(hr, error, "GetPrivateKeyStuff");

    hr = CoInitialize(NULL);
    if (S_OK != hr && S_FALSE != hr)
    {
        _JumpError(hr, error, "CoInitialize");
    }
    fCoInit = TRUE;

    if (1 >= g_fRPC)
    {
	hr = Request_Init(g_DispatchFlags, &diRequest);
	_JumpIfError(hr, error, "Request_Init");

	pdiRequest = &diRequest;
    }
    pwszConfig = g_pwszConfig;
    if (NULL == pwszConfig)
    {
	hr = ConfigGetConfig(g_DispatchFlags, CC_LOCALACTIVECONFIG, &strConfig);
	_JumpIfError(hr, error, "ConfigGetConfig");

	pwszConfig = strConfig;
    }


    TimeStartLastN = TimeStartTest = GetTickCount();
    while (TotalCount < g_MaximumCount)
    {
	DWORD TimeOneRequest;
	DWORD TimeRequestEnd;
	DWORD TimeElapsedLastN;

	hr = TestOneRequest(
			pKey,
			pdiRequest,
			pwszConfig,
			TotalCount + 1,
			&RequestId,
			&TimeOneRequest);
	if (S_OK != hr)
	{
	    WCHAR const *pwszMsg;

	    pwszMsg = myGetErrorMessageText(hr, TRUE);

	    CONSOLEPRINT3((
		    DBG_SS_CERTREQ,
		    "RequestId %u: %hs%ws\n",
		    RequestId,
		    HRESULT_FROM_WIN32(ERROR_INVALID_DATA) == hr?
			"Ignoring 7f length encoding: " : "",
		    NULL != pwszMsg? pwszMsg : L"Message retrieval Error"));
	    if (NULL != pwszMsg)
	    {
		LocalFree(const_cast<WCHAR *>(pwszMsg));
	    }
	}
	if (HRESULT_FROM_WIN32(ERROR_INVALID_DATA) != hr || !g_fIgnoreError)
	{
	    _JumpIfError(hr, error, "TestOneRequest");
	}

	TimeRequestEnd = GetTickCount();

	TimeRequestTotal += TimeOneRequest;
	TimeRequestLastN += TimeOneRequest;
	TimeElapsedTotal = TimeRequestEnd - TimeStartTest;
	TimeElapsedLastN = TimeRequestEnd - TimeStartLastN;

        TotalCount++;

	if (g_fTime)
	{
	    if (0 == g_IntervalCount || 0 == (TotalCount % g_IntervalCount))
	    {
		DWORD count;
		
		count = g_IntervalCount;
		if (0 == count)
		{
		    count = TotalCount;
		}

		TimeElapsedLastN = TimeRequestEnd - TimeStartLastN;

		wprintf(
		    L"RequestId %u: %u/%u Certs in %u/%u seconds (ave=%u/%u ms)\n",
		    RequestId,
		    count,
		    TotalCount,
		    MSTOSEC(TimeElapsedLastN),
		    MSTOSEC(TimeElapsedTotal),
		    TimeElapsedLastN/count,
		    TimeElapsedTotal/TotalCount);
		if (0 != g_IntervalCount)
		{
		    TimeRequestLastN = 0;
		    TimeStartLastN = GetTickCount();
		}
	    }
	}
    }

error:
    if (NULL != pKey)
    {
	LocalFree(pKey);
    }
    if (NULL != g_pRSAPublicKey)
    {
	LocalFree(g_pRSAPublicKey);
    }
    if (NULL != pdiRequest)
    {
	Request_Release(pdiRequest);
    }
    if (NULL != strConfig)
    {
	SysFreeString(strConfig);
    }
    if (fCoInit)
    {
	CoUninitialize();
    }

    if (0 != TotalCount)
    {
	wprintf(
	    L"\n%u Total Certificates in %u/%u seconds (request/elapsed time)\n",
	    TotalCount,
	    MSTOSEC(TimeRequestTotal),
	    MSTOSEC(TimeElapsedTotal));
	wprintf(
	    L"Certificates required average of %u/%u milliseconds "
		L"(request/elapsed time)\n",
	    TimeRequestTotal/TotalCount,
	    TimeElapsedTotal/TotalCount);
    }
    return(hr);
}


void
Usage(TCHAR *pwszError)
{
    wprintf(L"%ws\n", pwszError);
    wprintf(L"%ws\n", wszUsage);
    exit(1);
}


extern "C" int __cdecl
wmain(int argc, WCHAR *argv[])
{
    HRESULT hr;

    while (1 < argc && ('-' == argv[1][0] || '/' == argv[1][0]))
    {
	WCHAR *pwsz = argv[1];

	while (NULL != pwsz && *++pwsz != '\0')
	{
	    switch (*pwsz)
	    {
		case 'a':
		case 'A':
		    g_fIgnoreAccessDenied++;
		    break;

		case 'c':
		case 'C':
		    if (0 == lstrcmpi(pwsz, L"config"))
		    {
			if (1 >= argc)
			{
			    Usage(TEXT("Missing -config argument"));
			}
			g_pwszConfig = argv[2];
		    }
		    else
		    {
			if (2 >= argc || !iswdigit(argv[2][0]) || '\0' != pwsz[1])
			{
			    Usage(TEXT("Missing numeric -c argument"));
			}
			g_MaximumCount = _wtoi(argv[2]);
		    }
		    argc--;
		    argv++;
		    pwsz = NULL;
		    break;

		case 'd':
		case 'D':
		    g_fDebug++;
		    break;

		case 'i':
		case 'I':
		    g_fIgnoreError++;
		    break;

		case 'r':
		case 'R':
		    if (0 == lstrcmpi(pwsz, L"renewal"))
		    {
			g_fRenewal++;
			pwsz = NULL;
		    }
		    else
		    if (0 == lstrcmpi(pwsz, L"rpc"))
		    {
			g_fRPC++;
			if (0 == lstrcmp(pwsz, L"RPC"))
			{
			    g_fRPC++;
			}
			pwsz = NULL;
		    }
		    else
		    {
			g_fSave++;
		    }
		    break;

		case 'p':
		case 'P':
		    g_fPrintProperties++;
		    break;

		case 't':
		case 'T':
		    g_fTime++;
		    g_IntervalCount = 10;
		    if (2 < argc && iswdigit(argv[2][0]))
		    {
			if ('\0' != pwsz[1])
			{
			    Usage(TEXT("Missing numeric -t argument"));
			}
			g_IntervalCount = _wtoi(argv[2]);
			argc--;
			argv++;
			pwsz = NULL;
		    }
		    break;

		case 'z':
		case 'Z':
		    g_fAllowDups++;
		    g_crdnMax *= 5;
		    break;

		case 'm':
		case 'M':
		    g_fShowTime++;
		    break;

		case 'h':
		case 'H':
		default:
		    Usage(TEXT("CertGen Usage"));
	    }
	}
	argc--;
	argv++;
    }
    if (argc != 1)
    {
	Usage(TEXT("Extra arguments"));
    }

    if (g_fShowTime)
    {
        SYSTEMTIME st;
        GetSystemTime(&st);
        wprintf(L"Start time: %2i:%2i:%2i:%i\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    }

    hr = TestMain();

    if (g_fShowTime)
    {
        SYSTEMTIME st;

        GetSystemTime(&st);
        wprintf(L"End time: %2i:%2i:%2i:%i\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);

        wprintf(L"type any key to finish->");
        _getch();
        wprintf(L"\n");
    }
    myRegisterMemDump();
    return((int) hr);
}


// We need this to include RSA library
extern "C" BOOL
GenRandom(ULONG huid, BYTE *pbBuffer, size_t dwLength)
{
    wprintf(L"Error GenRandom called\n");
    ExitProcess(0);
    return(TRUE);
}