1269 lines
36 KiB
C++
1269 lines
36 KiB
C++
//+-------------------------------------------------------------------------
|
|
//
|
|
// RdMkCert - Remote Desktop internal certificate generator
|
|
//
|
|
// Generates NetMeeting default user certificates. The NetMeeting
|
|
// root key and certificate are stored as a program resource.
|
|
//
|
|
// ClausGi 7/29/98 created based on MAKECERT
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#include "global.h"
|
|
#include <memtrack.h>
|
|
|
|
#ifdef DEBUG
|
|
HDBGZONE ghDbgZone = NULL;
|
|
static PTCHAR _rgZonesNmMkCert[] = { TEXT("rdmkcert"), };
|
|
#endif /* DEBUG */
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// contants
|
|
//--------------------------------------------------------------------------
|
|
|
|
//allow max 10 extensions per certificate
|
|
#define MAX_EXT_CNT 10
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// globals
|
|
//--------------------------------------------------------------------------
|
|
|
|
WCHAR* g_wszSubjectKey = L"_RdMkCert";
|
|
WCHAR* g_wszSubjectStore = WSZNMSTORE;
|
|
DWORD g_dwSubjectStoreFlag = CERT_SYSTEM_STORE_CURRENT_USER;
|
|
|
|
DWORD g_dwIssuerKeySpec = AT_SIGNATURE;
|
|
DWORD g_dwSubjectKeySpec = AT_KEYEXCHANGE;
|
|
|
|
WCHAR *g_wszSubjectDisplayName = NULL; // BUGBUG set this?
|
|
|
|
LPWSTR g_wszIssuerProviderName = NULL;
|
|
LPWSTR g_wszSubjectProviderName = NULL;
|
|
|
|
WCHAR* g_wszSubjectX500Name;
|
|
|
|
DWORD g_dwProvType = PROV_RSA_FULL;
|
|
|
|
HMODULE hModule=NULL;
|
|
|
|
BOOL MakeCert(DWORD dwFlags);
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hDllInst, DWORD fdwReason, LPVOID)
|
|
{
|
|
switch (fdwReason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
{
|
|
hModule = hDllInst;
|
|
ASSERT (hModule != NULL);
|
|
DBGINIT(&ghDbgZone, _rgZonesNmMkCert);
|
|
DisableThreadLibraryCalls (hDllInst);
|
|
DBG_INIT_MEMORY_TRACKING(hDllInst);
|
|
break;
|
|
}
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
{
|
|
DBG_CHECK_MEMORY_TRACKING(hDllInst);
|
|
hModule = NULL;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
//
|
|
// X.509 cert strings must be from X.208 printable character set... this
|
|
// function enforces that.
|
|
//
|
|
|
|
static const char szPrintable[] = " '()+,-./:=?\""; // along with A-Za-z0-9
|
|
|
|
VOID MkPrintableString ( LPSTR szString )
|
|
{
|
|
CHAR * p = szString;
|
|
|
|
while ( *p )
|
|
{
|
|
if (!(('a' <= *p && *p <='z') ||
|
|
('A' <= *p && *p <='Z') ||
|
|
('0' <= *p && *p <='9') ||
|
|
_StrChr(szPrintable,*p)))
|
|
{
|
|
*p = '-';
|
|
}
|
|
p++;
|
|
}
|
|
}
|
|
|
|
DWORD WINAPI NmMakeCert( LPCSTR szFirstName,
|
|
LPCSTR szLastName,
|
|
LPCSTR szEmailName,
|
|
LPCSTR szCity,
|
|
LPCSTR szCountry,
|
|
DWORD flags)
|
|
{
|
|
DWORD dwRet = -1;
|
|
|
|
WARNING_OUT(("NmMakeCert called"));
|
|
|
|
// Form the unencoded X500 subject string. It would be nice to
|
|
// use official constants for the below... CertRDNValueToString?
|
|
|
|
UINT cbX500Name = ( szFirstName ? lstrlen(szFirstName) : 0 ) +
|
|
( szLastName ? lstrlen(szLastName) : 0 ) +
|
|
( szEmailName ? lstrlen(szEmailName) : 0 ) +
|
|
( szCity ? lstrlen(szCity) : 0 ) +
|
|
( szCountry ? lstrlen(szCountry) : 0 ) +
|
|
128; // Extra is for RDN OID strings: CN= etc.
|
|
|
|
char * pX500Name = new char[cbX500Name];
|
|
|
|
if ( NULL == pX500Name )
|
|
{
|
|
ERROR_OUT(("couldn't allocate %d bytes for x500 name", cbX500Name));
|
|
goto cleanup;
|
|
}
|
|
|
|
ASSERT( ( szFirstName && *szFirstName ) || ( szLastName && *szLastName ) );
|
|
|
|
wsprintf( pX500Name, "CN=\"%s %s\"", szFirstName ? szFirstName : "", szLastName ? szLastName : "" );
|
|
|
|
if ( szEmailName && *szEmailName )
|
|
wsprintf( pX500Name + lstrlen(pX500Name), ", E=\"%s\"", szEmailName );
|
|
|
|
if ( szCity && *szCity )
|
|
wsprintf( pX500Name + lstrlen(pX500Name), ", S=\"%s\"", szCity );
|
|
|
|
if ( szCountry && *szCountry )
|
|
wsprintf( pX500Name + lstrlen(pX500Name), ", C=\"%s\"", szCountry );
|
|
|
|
MkPrintableString ( pX500Name );
|
|
|
|
g_wszSubjectX500Name = AnsiToUnicode ( pX500Name );
|
|
|
|
ASSERT(g_wszSubjectX500Name);
|
|
|
|
if ( flags & NMMKCERT_F_LOCAL_MACHINE )
|
|
{
|
|
// We are being asked to generate a local machine cert...
|
|
// change the subject store flag and the key container name
|
|
g_dwSubjectStoreFlag = CERT_SYSTEM_STORE_LOCAL_MACHINE;
|
|
g_wszSubjectKey = L"_RdMkMchCert";
|
|
}
|
|
|
|
// If we're on NT5 we have to generate the cert using the
|
|
// PROV_RSA_SCHANNEL provider, on other platforms this provider type
|
|
// doesn't exist.
|
|
|
|
OSVERSIONINFO osVersion;
|
|
|
|
ZeroMemory(&osVersion, sizeof(osVersion));
|
|
osVersion.dwOSVersionInfoSize = sizeof(osVersion);
|
|
GetVersionEx(&osVersion);
|
|
|
|
if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
osVersion.dwMajorVersion >= 5)
|
|
{
|
|
g_dwProvType = PROV_RSA_SCHANNEL;
|
|
}
|
|
|
|
// Get to work and make the certificate
|
|
if (!MakeCert(flags))
|
|
{
|
|
WARNING_OUT(("NmMakeCert failed."));
|
|
}
|
|
else
|
|
{
|
|
dwRet = 1;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
if ( NULL != g_wszSubjectX500Name )
|
|
{
|
|
delete g_wszSubjectX500Name;
|
|
}
|
|
|
|
if ( NULL != pX500Name )
|
|
{
|
|
delete pX500Name;
|
|
}
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
|
|
// RUNDLL entry point for certificate uninstall... the prototype is given
|
|
// by RUNDLL32.EXE requirements!
|
|
void CALLBACK NmMakeCertCleanup ( HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow )
|
|
{
|
|
// Clean up exisint certs and private keys
|
|
MakeCert(NMMKCERT_F_CLEANUP_ONLY);
|
|
g_dwSubjectStoreFlag = CERT_SYSTEM_STORE_LOCAL_MACHINE;
|
|
g_wszSubjectKey = L"_RdMkMchCert";
|
|
MakeCert(NMMKCERT_F_LOCAL_MACHINE|NMMKCERT_F_CLEANUP_ONLY);
|
|
}
|
|
|
|
|
|
//+=========================================================================
|
|
// Local Support Functions
|
|
//==========================================================================
|
|
|
|
//+=========================================================================
|
|
// MakeCert support functions
|
|
//==========================================================================
|
|
|
|
BOOL VerifyIssuerKey( IN HCRYPTPROV hProv,
|
|
IN PCERT_PUBLIC_KEY_INFO pIssuerKeyInfo);
|
|
HCRYPTPROV GetSubjectProv(OUT LPWSTR *ppwszTmpContainer);
|
|
|
|
BOOL GetPublicKey(
|
|
HCRYPTPROV hProv,
|
|
PCERT_PUBLIC_KEY_INFO *ppPubKeyInfo
|
|
);
|
|
BOOL EncodeSubject(
|
|
OUT BYTE **ppbEncoded,
|
|
IN OUT DWORD *pcbEncoded
|
|
);
|
|
BOOL CreateSpcCommonName(
|
|
OUT BYTE **ppbEncoded,
|
|
IN OUT DWORD *pcbEncoded
|
|
);
|
|
BOOL CreateEnhancedKeyUsage(
|
|
OUT BYTE **ppbEncoded,
|
|
IN OUT DWORD *pcbEncoded
|
|
);
|
|
|
|
BOOL SaveCertToStore(HCRYPTPROV hProv,
|
|
HCERTSTORE hStore,
|
|
DWORD dwFlag,
|
|
BYTE *pbEncodedCert,
|
|
DWORD cbEncodedCert,
|
|
LPWSTR wszPvk,
|
|
DWORD dwKeySpecification,
|
|
LPWSTR wszCapiProv,
|
|
DWORD dwCapiProvType);
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Get the root's certificate from the program's resources
|
|
//--------------------------------------------------------------------------
|
|
PCCERT_CONTEXT GetRootCertContext()
|
|
{
|
|
PCCERT_CONTEXT pCert = NULL;
|
|
HRSRC hRes;
|
|
|
|
//
|
|
// The root certificate is stored as a resource of ours.
|
|
// Load it...
|
|
//
|
|
if (0 != (hRes = FindResource(hModule, MAKEINTRESOURCE(IDR_ROOTCERTIFICATE),
|
|
"CER"))) {
|
|
HGLOBAL hglobRes;
|
|
if (NULL != (hglobRes = LoadResource(hModule, hRes))) {
|
|
BYTE *pbRes;
|
|
DWORD cbRes;
|
|
|
|
cbRes = SizeofResource(hModule, hRes);
|
|
pbRes = (BYTE *) LockResource(hglobRes);
|
|
|
|
if (cbRes && pbRes)
|
|
pCert = CertCreateCertificateContext(X509_ASN_ENCODING,
|
|
pbRes, cbRes);
|
|
if ( NULL == pCert )
|
|
{
|
|
DWORD dwError = GetLastError();
|
|
}
|
|
|
|
UnlockResource(hglobRes);
|
|
FreeResource(hglobRes);
|
|
}
|
|
}
|
|
|
|
if (pCert == NULL)
|
|
{
|
|
ERROR_OUT(("Error creating root cert: %x", GetLastError()));
|
|
}
|
|
return pCert;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Get the root's private key from the program's resources and create
|
|
// a temporary key provider container
|
|
//--------------------------------------------------------------------------
|
|
HCRYPTPROV GetRootProv(OUT LPWSTR *ppwszTmpContainer)
|
|
{
|
|
HCRYPTPROV hProv = 0;
|
|
HRSRC hRes;
|
|
WCHAR wszRootSig[] = L"Root Signature";
|
|
|
|
*ppwszTmpContainer = NULL;
|
|
|
|
if (0 != (hRes = FindResource(hModule,MAKEINTRESOURCE(IDR_PVKROOT),"PVK")))
|
|
{
|
|
HGLOBAL hglobRes;
|
|
if (NULL != (hglobRes = LoadResource(hModule, hRes))) {
|
|
BYTE *pbRes;
|
|
DWORD cbRes;
|
|
|
|
cbRes = SizeofResource(hModule, hRes);
|
|
pbRes = (BYTE *) LockResource(hglobRes);
|
|
if (cbRes && pbRes) {
|
|
PvkPrivateKeyAcquireContextFromMemory(
|
|
g_wszIssuerProviderName,
|
|
PROV_RSA_FULL,
|
|
pbRes,
|
|
cbRes,
|
|
NULL, // hwndOwner
|
|
wszRootSig,
|
|
&g_dwIssuerKeySpec,
|
|
&hProv
|
|
);
|
|
}
|
|
UnlockResource(hglobRes);
|
|
FreeResource(hglobRes);
|
|
}
|
|
}
|
|
|
|
if (hProv == 0)
|
|
{
|
|
ERROR_OUT(("couldn't create root key provider: %x", GetLastError()));
|
|
}
|
|
return hProv;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Make the subject certificate. If the subject doesn't have a private
|
|
// key, then, create.
|
|
//--------------------------------------------------------------------------
|
|
BOOL MakeCert(DWORD dwFlags)
|
|
{
|
|
BOOL fResult;
|
|
|
|
HCRYPTPROV hIssuerProv = 0;
|
|
LPWSTR pwszTmpIssuerContainer = NULL;
|
|
PCCERT_CONTEXT pIssuerCertContext = NULL;
|
|
PCERT_INFO pIssuerCert =NULL; // not allocated
|
|
|
|
HCRYPTPROV hSubjectProv = 0;
|
|
LPWSTR pwszTmpSubjectContainer = NULL;
|
|
|
|
PCERT_PUBLIC_KEY_INFO pSubjectPubKeyInfo = NULL; // not allocated
|
|
PCERT_PUBLIC_KEY_INFO pAllocSubjectPubKeyInfo = NULL;
|
|
BYTE *pbSubjectEncoded = NULL;
|
|
DWORD cbSubjectEncoded =0;
|
|
BYTE *pbSpcCommonNameEncoded = NULL;
|
|
DWORD cbSpcCommonNameEncoded =0;
|
|
BYTE *pbCertEncoded = NULL;
|
|
DWORD cbCertEncoded =0;
|
|
BYTE *pbEKUEncoded = NULL;
|
|
DWORD cbEKUEncoded = 0;
|
|
|
|
CERT_INFO Cert;
|
|
GUID SerialNumber;
|
|
HCERTSTORE hStore=NULL;
|
|
|
|
CERT_EXTENSION rgExt[MAX_EXT_CNT];
|
|
DWORD cExt = 0;
|
|
|
|
CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm = {
|
|
szOID_RSA_MD5RSA, 0, 0
|
|
};
|
|
|
|
if (0 == (hSubjectProv = GetSubjectProv(&pwszTmpSubjectContainer)))
|
|
goto ErrorReturn;
|
|
|
|
|
|
#define TEMP_CLEAN_CODE
|
|
#ifdef TEMP_CLEAN_CODE
|
|
// open the system store where we used to generate certs
|
|
hStore=CertOpenStore(CERT_STORE_PROV_SYSTEM_W,
|
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
|
hSubjectProv,
|
|
CERT_STORE_NO_CRYPT_RELEASE_FLAG | g_dwSubjectStoreFlag,
|
|
L"MY" );
|
|
|
|
if ( hStore )
|
|
{
|
|
//
|
|
// Delete all old certs
|
|
//
|
|
PCCERT_CONTEXT pCertContext = NULL;
|
|
|
|
// Clear out any certificate(s) we may have added before
|
|
while ( pCertContext = CertEnumCertificatesInStore(
|
|
hStore, (PCERT_CONTEXT)pCertContext ))
|
|
{
|
|
DWORD dwMagic;
|
|
DWORD cbMagic;
|
|
|
|
cbMagic = sizeof(dwMagic);
|
|
|
|
if (CertGetCertificateContextProperty(pCertContext,
|
|
CERT_FIRST_USER_PROP_ID, &dwMagic, &cbMagic) &&
|
|
cbMagic == sizeof(dwMagic) && dwMagic == NMMKCERT_MAGIC )
|
|
{
|
|
CertDeleteCertificateFromStore(pCertContext);
|
|
// Restart the enumeration
|
|
pCertContext = NULL;
|
|
continue;
|
|
}
|
|
}
|
|
CertCloseStore(hStore,0);
|
|
}
|
|
#endif // TEMP_CLEAN_CODE
|
|
|
|
// open a new cert store
|
|
hStore=CertOpenStore(CERT_STORE_PROV_SYSTEM_W,
|
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
|
hSubjectProv,
|
|
CERT_STORE_NO_CRYPT_RELEASE_FLAG | g_dwSubjectStoreFlag,
|
|
g_wszSubjectStore);
|
|
|
|
if(hStore==NULL)
|
|
goto ErrorReturn;
|
|
|
|
// Empty the store
|
|
PCCERT_CONTEXT pCertContext;
|
|
while ( pCertContext = CertEnumCertificatesInStore ( hStore, NULL ))
|
|
{
|
|
if ( !CertDeleteCertificateFromStore ( pCertContext ))
|
|
{
|
|
WARNING_OUT(("Failed to delete certificate: %x", GetLastError()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If NMMKCERT_F_CLEANUP_ONLY is set, we are done
|
|
if ( dwFlags & NMMKCERT_F_CLEANUP_ONLY )
|
|
{
|
|
// We've just deleted the existing certs, now delete the
|
|
// private key container and exit.
|
|
CryptAcquireContextU(
|
|
&hSubjectProv,
|
|
g_wszSubjectKey,
|
|
g_wszSubjectProviderName,
|
|
g_dwProvType,
|
|
CRYPT_DELETEKEYSET |
|
|
( g_dwSubjectStoreFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE ?
|
|
CRYPT_MACHINE_KEYSET : 0 ));
|
|
fResult = TRUE;
|
|
goto CommonReturn;
|
|
}
|
|
|
|
//
|
|
// Get access to the subject's (public) key, creating it if necessary
|
|
//
|
|
if (!GetPublicKey(hSubjectProv, &pAllocSubjectPubKeyInfo))
|
|
goto ErrorReturn;
|
|
pSubjectPubKeyInfo = pAllocSubjectPubKeyInfo;
|
|
|
|
|
|
//
|
|
// Encode the subject name
|
|
//
|
|
if (!EncodeSubject(&pbSubjectEncoded, &cbSubjectEncoded))
|
|
goto ErrorReturn;
|
|
|
|
//
|
|
// Get access to the issuer's (private) key
|
|
//
|
|
hIssuerProv= GetRootProv(&pwszTmpIssuerContainer);
|
|
|
|
if (NULL == (pIssuerCertContext = GetRootCertContext()))
|
|
goto ErrorReturn;
|
|
|
|
pIssuerCert = pIssuerCertContext->pCertInfo;
|
|
|
|
if (!VerifyIssuerKey(hIssuerProv, &pIssuerCert->SubjectPublicKeyInfo))
|
|
goto ErrorReturn;
|
|
|
|
//
|
|
// Update the CERT_INFO
|
|
//
|
|
ClearStruct(&Cert);
|
|
Cert.dwVersion = CERT_V3;
|
|
|
|
CoCreateGuid(&SerialNumber);
|
|
Cert.SerialNumber.pbData = (BYTE *) &SerialNumber;
|
|
Cert.SerialNumber.cbData = sizeof(SerialNumber);
|
|
|
|
Cert.SignatureAlgorithm = SignatureAlgorithm;
|
|
Cert.Issuer.pbData = pIssuerCert->Subject.pbData;
|
|
Cert.Issuer.cbData = pIssuerCert->Subject.cbData;
|
|
|
|
{
|
|
SYSTEMTIME st;
|
|
|
|
// Valid starting now...
|
|
GetSystemTimeAsFileTime(&Cert.NotBefore);
|
|
|
|
// Ending in 2039 (arbitrarily)
|
|
ClearStruct(&st);
|
|
st.wYear = 2039;
|
|
st.wMonth = 12;
|
|
st.wDay = 31;
|
|
st.wHour = 23;
|
|
st.wMinute= 59;
|
|
st.wSecond= 59;
|
|
SystemTimeToFileTime(&st, &Cert.NotAfter);
|
|
}
|
|
|
|
Cert.Subject.pbData = pbSubjectEncoded;
|
|
Cert.Subject.cbData = cbSubjectEncoded;
|
|
Cert.SubjectPublicKeyInfo = *pSubjectPubKeyInfo;
|
|
|
|
// Cert Extensions
|
|
|
|
if (!CreateEnhancedKeyUsage(
|
|
&pbEKUEncoded,
|
|
&cbEKUEncoded))
|
|
goto ErrorReturn;
|
|
|
|
rgExt[cExt].pszObjId = szOID_ENHANCED_KEY_USAGE;
|
|
rgExt[cExt].fCritical = FALSE;
|
|
rgExt[cExt].Value.pbData = pbEKUEncoded;
|
|
rgExt[cExt].Value.cbData = cbEKUEncoded;
|
|
cExt++;
|
|
|
|
if (g_wszSubjectDisplayName) {
|
|
if (!CreateSpcCommonName(
|
|
&pbSpcCommonNameEncoded,
|
|
&cbSpcCommonNameEncoded))
|
|
goto ErrorReturn;
|
|
rgExt[cExt].pszObjId = szOID_COMMON_NAME;
|
|
rgExt[cExt].fCritical = FALSE;
|
|
rgExt[cExt].Value.pbData = pbSpcCommonNameEncoded;
|
|
rgExt[cExt].Value.cbData = cbSpcCommonNameEncoded;
|
|
cExt++;
|
|
}
|
|
|
|
Cert.rgExtension = rgExt;
|
|
Cert.cExtension = cExt;
|
|
|
|
//
|
|
// Sign and encode the certificate
|
|
//
|
|
cbCertEncoded = 0;
|
|
CryptSignAndEncodeCertificate(
|
|
hIssuerProv,
|
|
g_dwIssuerKeySpec,
|
|
X509_ASN_ENCODING,
|
|
X509_CERT_TO_BE_SIGNED,
|
|
&Cert,
|
|
&Cert.SignatureAlgorithm,
|
|
NULL, // pvHashAuxInfo
|
|
NULL, // pbEncoded
|
|
&cbCertEncoded
|
|
);
|
|
if (cbCertEncoded == 0) {
|
|
ERROR_OUT(("CryptSignAndEncodeCertificate failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
pbCertEncoded = new BYTE[cbCertEncoded];
|
|
if (pbCertEncoded == NULL) goto ErrorReturn;
|
|
if (!CryptSignAndEncodeCertificate(
|
|
hIssuerProv,
|
|
g_dwIssuerKeySpec,
|
|
X509_ASN_ENCODING,
|
|
X509_CERT_TO_BE_SIGNED,
|
|
&Cert,
|
|
&Cert.SignatureAlgorithm,
|
|
NULL, // pvHashAuxInfo
|
|
pbCertEncoded,
|
|
&cbCertEncoded
|
|
)) {
|
|
ERROR_OUT(("CryptSignAndEncodeCertificate(2) failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
// Output the encoded certificate to an cerificate store
|
|
|
|
ASSERT(g_wszSubjectStore);
|
|
ASSERT(AT_KEYEXCHANGE == g_dwSubjectKeySpec);
|
|
|
|
if((!SaveCertToStore(hSubjectProv,
|
|
hStore,
|
|
g_dwSubjectStoreFlag,
|
|
pbCertEncoded,
|
|
cbCertEncoded,
|
|
g_wszSubjectKey,
|
|
g_dwSubjectKeySpec,
|
|
g_wszSubjectProviderName,
|
|
g_dwProvType)))
|
|
{
|
|
ERROR_OUT(("SaveCertToStore failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
|
|
}
|
|
|
|
fResult = TRUE;
|
|
goto CommonReturn;
|
|
|
|
ErrorReturn:
|
|
fResult = FALSE;
|
|
CommonReturn:
|
|
|
|
PvkFreeCryptProv(hSubjectProv, g_wszSubjectProviderName,
|
|
g_dwProvType,pwszTmpSubjectContainer);
|
|
|
|
//free the cert store
|
|
if(hStore)
|
|
CertCloseStore(hStore, 0);
|
|
if (pIssuerCertContext)
|
|
CertFreeCertificateContext(pIssuerCertContext);
|
|
if (pAllocSubjectPubKeyInfo)
|
|
delete (pAllocSubjectPubKeyInfo);
|
|
if (pbSubjectEncoded)
|
|
delete (pbSubjectEncoded);
|
|
if (pbEKUEncoded)
|
|
delete (pbEKUEncoded);
|
|
if (pbSpcCommonNameEncoded)
|
|
delete (pbSpcCommonNameEncoded);
|
|
if (pbCertEncoded)
|
|
delete (pbCertEncoded);
|
|
if (hIssuerProv)
|
|
CryptReleaseContext(hIssuerProv,0);
|
|
|
|
return fResult;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// save the certificate to a certificate store. Attach private key information
|
|
// to the certificate
|
|
//--------------------------------------------------------------------------
|
|
BOOL SaveCertToStore(
|
|
HCRYPTPROV hProv,
|
|
HCERTSTORE hStore, DWORD dwFlag,
|
|
BYTE *pbEncodedCert, DWORD cbEncodedCert,
|
|
LPWSTR wszPvk,
|
|
DWORD dwKeySpecification,
|
|
LPWSTR wszCapiProv, DWORD dwCapiProvType)
|
|
{
|
|
BOOL fResult=FALSE;
|
|
PCCERT_CONTEXT pCertContext=NULL;
|
|
CRYPT_KEY_PROV_INFO KeyProvInfo;
|
|
|
|
HCRYPTPROV hDefaultProvName=NULL;
|
|
DWORD cbData=0;
|
|
LPSTR pszName=NULL;
|
|
LPWSTR pwszName=NULL;
|
|
|
|
//init
|
|
ClearStruct(&KeyProvInfo);
|
|
|
|
//add the encoded certificate to store
|
|
if(!CertAddEncodedCertificateToStore(
|
|
hStore,
|
|
X509_ASN_ENCODING,
|
|
pbEncodedCert,
|
|
cbEncodedCert,
|
|
CERT_STORE_ADD_REPLACE_EXISTING,
|
|
&pCertContext))
|
|
goto CLEANUP;
|
|
|
|
//add properties to the certificate
|
|
KeyProvInfo.pwszContainerName=wszPvk;
|
|
KeyProvInfo.pwszProvName=wszCapiProv,
|
|
KeyProvInfo.dwProvType=dwCapiProvType,
|
|
KeyProvInfo.dwKeySpec=dwKeySpecification;
|
|
|
|
if ( g_dwSubjectStoreFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE )
|
|
{
|
|
// If this is a local machine cert, set the keyset flags
|
|
// indicating that the private key will be under HKLM
|
|
KeyProvInfo.dwFlags = CRYPT_MACHINE_KEYSET;
|
|
}
|
|
|
|
ASSERT(AT_KEYEXCHANGE == dwKeySpecification);
|
|
|
|
//if wszCapiProv is NULL, we get the default provider name
|
|
if(NULL==wszCapiProv)
|
|
{
|
|
//get the default provider
|
|
if(CryptAcquireContext(&hDefaultProvName,
|
|
NULL,
|
|
NULL,
|
|
KeyProvInfo.dwProvType,
|
|
CRYPT_VERIFYCONTEXT))
|
|
{
|
|
|
|
//get the provider name
|
|
if(CryptGetProvParam(hDefaultProvName,
|
|
PP_NAME,
|
|
NULL,
|
|
&cbData,
|
|
0) && (0!=cbData))
|
|
{
|
|
|
|
if(pszName= new CHAR[cbData])
|
|
{
|
|
if(CryptGetProvParam(hDefaultProvName,
|
|
PP_NAME,
|
|
(BYTE *)pszName,
|
|
&cbData,
|
|
0))
|
|
{
|
|
pwszName= AnsiToUnicode(pszName);
|
|
|
|
KeyProvInfo.pwszProvName=pwszName;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//free the provider as we want
|
|
if(hDefaultProvName)
|
|
CryptReleaseContext(hDefaultProvName, 0);
|
|
|
|
hDefaultProvName=NULL;
|
|
|
|
//add property related to the key container
|
|
if(!CertSetCertificateContextProperty(
|
|
pCertContext,
|
|
CERT_KEY_PROV_INFO_PROP_ID,
|
|
0,
|
|
&KeyProvInfo))
|
|
goto CLEANUP;
|
|
|
|
//
|
|
// Load the display name from resource and create a blob to
|
|
// set the cert friendly name.
|
|
//
|
|
CHAR szFriendlyName[128];
|
|
|
|
if (!LoadString(hModule, IDS_DEFNAME, szFriendlyName,
|
|
sizeof(szFriendlyName)))
|
|
{
|
|
ERROR_OUT(("LoadString failed: %d", GetLastError()));
|
|
goto CLEANUP;
|
|
}
|
|
|
|
WCHAR *pwszFriendlyName;
|
|
|
|
pwszFriendlyName = AnsiToUnicode ( szFriendlyName );
|
|
|
|
if ( NULL == pwszFriendlyName )
|
|
{
|
|
ERROR_OUT(("AnsiToUnicode failed"));
|
|
goto CLEANUP;
|
|
}
|
|
|
|
CRYPT_DATA_BLOB FriendlyName;
|
|
|
|
FriendlyName.pbData = (PBYTE)pwszFriendlyName;
|
|
FriendlyName.cbData = ( lstrlenW(pwszFriendlyName) + 1 ) *
|
|
sizeof(WCHAR);
|
|
|
|
if(!CertSetCertificateContextProperty(
|
|
pCertContext,
|
|
CERT_FRIENDLY_NAME_PROP_ID,
|
|
0,
|
|
&FriendlyName))
|
|
goto CLEANUP;
|
|
|
|
//
|
|
// Add magic ID
|
|
//
|
|
CRYPT_DATA_BLOB MagicBlob;
|
|
DWORD dwMagic;
|
|
|
|
dwMagic = NMMKCERT_MAGIC;
|
|
MagicBlob.pbData = (PBYTE)&dwMagic;
|
|
MagicBlob.cbData = sizeof(dwMagic);
|
|
|
|
if(!CertSetCertificateContextProperty(
|
|
pCertContext,
|
|
CERT_FIRST_USER_PROP_ID,
|
|
0,
|
|
&MagicBlob))
|
|
goto CLEANUP;
|
|
|
|
fResult=TRUE;
|
|
|
|
CLEANUP:
|
|
|
|
if (pwszFriendlyName)
|
|
delete pwszFriendlyName;
|
|
|
|
//free the cert context
|
|
if(pCertContext)
|
|
CertFreeCertificateContext(pCertContext);
|
|
|
|
if(pszName)
|
|
delete (pszName);
|
|
|
|
if(pwszName)
|
|
delete pwszName;
|
|
|
|
if(hDefaultProvName)
|
|
CryptReleaseContext(hDefaultProvName, 0);
|
|
|
|
return fResult;
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Verify the issuer's certificate. The public key in the certificate
|
|
// must match the public key associated with the private key in the
|
|
// issuer's provider
|
|
//--------------------------------------------------------------------------
|
|
BOOL VerifyIssuerKey(
|
|
IN HCRYPTPROV hProv,
|
|
IN PCERT_PUBLIC_KEY_INFO pIssuerKeyInfo
|
|
)
|
|
{
|
|
BOOL fResult;
|
|
PCERT_PUBLIC_KEY_INFO pPubKeyInfo = NULL;
|
|
DWORD cbPubKeyInfo;
|
|
|
|
// Get issuer's public key
|
|
cbPubKeyInfo = 0;
|
|
CryptExportPublicKeyInfo(
|
|
hProv,
|
|
g_dwIssuerKeySpec,
|
|
X509_ASN_ENCODING,
|
|
NULL, // pPubKeyInfo
|
|
&cbPubKeyInfo
|
|
);
|
|
if (cbPubKeyInfo == 0)
|
|
{
|
|
ERROR_OUT(("CryptExportPublicKeyInfo failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
if (NULL == (pPubKeyInfo = (PCERT_PUBLIC_KEY_INFO) new BYTE[cbPubKeyInfo]))
|
|
goto ErrorReturn;
|
|
if (!CryptExportPublicKeyInfo(
|
|
hProv,
|
|
g_dwIssuerKeySpec,
|
|
X509_ASN_ENCODING,
|
|
pPubKeyInfo,
|
|
&cbPubKeyInfo
|
|
)) {
|
|
ERROR_OUT(("CrypteExportPublicKeyInfo(2) failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
if (!CertComparePublicKeyInfo(
|
|
X509_ASN_ENCODING,
|
|
pIssuerKeyInfo,
|
|
pPubKeyInfo)) {
|
|
// BUGBUG:: This might be the test root with an incorrectly
|
|
// encoded public key. Convert to the capi representation and
|
|
// compare.
|
|
BYTE rgProvKey[256]; //BUGBUG needs appropriate constant or calc
|
|
BYTE rgCertKey[256]; //BUGBUG needs appropriate constant or calc
|
|
DWORD cbProvKey = sizeof(rgProvKey);
|
|
DWORD cbCertKey = sizeof(rgCertKey);
|
|
|
|
if (!CryptDecodeObject(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
|
|
pIssuerKeyInfo->PublicKey.pbData,
|
|
pIssuerKeyInfo->PublicKey.cbData,
|
|
0, // dwFlags
|
|
rgProvKey,
|
|
&cbProvKey) ||
|
|
!CryptDecodeObject(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB,
|
|
pPubKeyInfo->PublicKey.pbData,
|
|
pPubKeyInfo->PublicKey.cbData,
|
|
0, // dwFlags
|
|
rgCertKey,
|
|
&cbCertKey) ||
|
|
cbProvKey == 0 || cbProvKey != cbCertKey ||
|
|
memcmp(rgProvKey, rgCertKey, cbProvKey) != 0) {
|
|
ERROR_OUT(("mismatch: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
}
|
|
|
|
fResult = TRUE;
|
|
goto CommonReturn;
|
|
|
|
ErrorReturn:
|
|
fResult = FALSE;
|
|
CommonReturn:
|
|
if (pPubKeyInfo)
|
|
delete (pPubKeyInfo);
|
|
return fResult;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Get the subject's private key provider
|
|
//--------------------------------------------------------------------------
|
|
HCRYPTPROV GetSubjectProv(OUT LPWSTR *ppwszTmpContainer)
|
|
{
|
|
HCRYPTPROV hProv=0;
|
|
WCHAR wszKeyName[40] = L"Subject Key";
|
|
int ids;
|
|
WCHAR *wszRegKeyName=NULL;
|
|
BOOL fResult;
|
|
HCRYPTKEY hKey=NULL;
|
|
GUID TmpContainerUuid;
|
|
|
|
//try to get the hProv from the private key container
|
|
if(S_OK != PvkGetCryptProv(NULL,
|
|
wszKeyName,
|
|
g_wszSubjectProviderName,
|
|
g_dwProvType,
|
|
NULL,
|
|
g_wszSubjectKey,
|
|
&g_dwSubjectKeySpec,
|
|
ppwszTmpContainer,
|
|
&hProv))
|
|
hProv=0;
|
|
|
|
//generate the private keys
|
|
if (0 == hProv)
|
|
{
|
|
//now that we have to generate private keys, generate
|
|
//AT_KEYEXCHANGE key
|
|
|
|
// If there is an existing container with the name of the
|
|
// one we are about to create, attempt to delete it first so
|
|
// that creating it won't fail. This should only happen if the
|
|
// container exists but we were unable to acquire a context to
|
|
// it previously.
|
|
CryptAcquireContextU(
|
|
&hProv,
|
|
g_wszSubjectKey,
|
|
g_wszSubjectProviderName,
|
|
g_dwProvType,
|
|
CRYPT_DELETEKEYSET |
|
|
( g_dwSubjectStoreFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE ?
|
|
CRYPT_MACHINE_KEYSET : 0 ));
|
|
|
|
// Open a new key container
|
|
if (!CryptAcquireContextU(
|
|
&hProv,
|
|
g_wszSubjectKey,
|
|
g_wszSubjectProviderName,
|
|
g_dwProvType,
|
|
CRYPT_NEWKEYSET |
|
|
( g_dwSubjectStoreFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE ?
|
|
CRYPT_MACHINE_KEYSET : 0 )))
|
|
{
|
|
ERROR_OUT(("CryptAcquireContext failed: %x", GetLastError()));
|
|
goto CreateKeyError;
|
|
}
|
|
|
|
//generate new keys in the key container - make sure its EXPORTABLE
|
|
//for SCHANNEL! (Note: remove that when SCHANNEL no longer needs it).
|
|
if (!CryptGenKey( hProv, g_dwSubjectKeySpec, CRYPT_EXPORTABLE, &hKey))
|
|
{
|
|
ERROR_OUT(("CryptGenKey failed: %x", GetLastError()));
|
|
goto CreateKeyError;
|
|
}
|
|
else
|
|
CryptDestroyKey(hKey);
|
|
|
|
//try to get the user key
|
|
if (CryptGetUserKey( hProv, g_dwSubjectKeySpec, &hKey))
|
|
{
|
|
CryptDestroyKey(hKey);
|
|
}
|
|
else
|
|
{
|
|
// Doesn't have the specified public key
|
|
CryptReleaseContext(hProv, 0);
|
|
hProv=0;
|
|
}
|
|
|
|
if (0 == hProv )
|
|
{
|
|
ERROR_OUT(("sub key error: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
} //hProv==0
|
|
|
|
goto CommonReturn;
|
|
|
|
CreateKeyError:
|
|
ErrorReturn:
|
|
if (hProv)
|
|
{
|
|
CryptReleaseContext(hProv, 0);
|
|
hProv = 0;
|
|
}
|
|
CommonReturn:
|
|
if(wszRegKeyName)
|
|
delete (wszRegKeyName);
|
|
|
|
return hProv;
|
|
}
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Allocate and get the public key info for the provider
|
|
//--------------------------------------------------------------------------
|
|
BOOL GetPublicKey(
|
|
HCRYPTPROV hProv,
|
|
PCERT_PUBLIC_KEY_INFO *ppPubKeyInfo
|
|
)
|
|
{
|
|
BOOL fResult;
|
|
|
|
PCERT_PUBLIC_KEY_INFO pPubKeyInfo = NULL;
|
|
DWORD cbPubKeyInfo;
|
|
|
|
cbPubKeyInfo = 0;
|
|
CryptExportPublicKeyInfo(
|
|
hProv,
|
|
g_dwSubjectKeySpec,
|
|
X509_ASN_ENCODING,
|
|
NULL, // pPubKeyInfo
|
|
&cbPubKeyInfo
|
|
);
|
|
if (cbPubKeyInfo == 0) {
|
|
ERROR_OUT(("CryptExportPublicKeyInfo failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
if (NULL == (pPubKeyInfo = (PCERT_PUBLIC_KEY_INFO) new BYTE[cbPubKeyInfo]))
|
|
goto ErrorReturn;
|
|
if (!CryptExportPublicKeyInfo(
|
|
hProv,
|
|
g_dwSubjectKeySpec,
|
|
X509_ASN_ENCODING,
|
|
pPubKeyInfo,
|
|
&cbPubKeyInfo
|
|
)) {
|
|
ERROR_OUT(("CryptExportPublicKeyInfo(2) failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
fResult = TRUE;
|
|
goto CommonReturn;
|
|
|
|
ErrorReturn:
|
|
fResult = FALSE;
|
|
if (pPubKeyInfo) {
|
|
delete (pPubKeyInfo);
|
|
pPubKeyInfo = NULL;
|
|
}
|
|
CommonReturn:
|
|
*ppPubKeyInfo = pPubKeyInfo;
|
|
return fResult;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Convert and encode the subject's X500 formatted name
|
|
//--------------------------------------------------------------------------
|
|
BOOL EncodeSubject(
|
|
OUT BYTE **ppbEncoded,
|
|
IN OUT DWORD *pcbEncoded
|
|
)
|
|
{
|
|
BOOL fResult;
|
|
DWORD cbEncodedSubject=0;
|
|
BYTE *pbEncodedSubject=NULL;
|
|
BYTE *pbEncoded = NULL;
|
|
DWORD cbEncoded;
|
|
|
|
//encode the wszSubjectX500Name into an encoded X509_NAME
|
|
|
|
if(!CertStrToNameW(
|
|
X509_ASN_ENCODING,
|
|
g_wszSubjectX500Name,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&cbEncodedSubject,
|
|
NULL))
|
|
{
|
|
ERROR_OUT(("CertStrToNameW failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
pbEncodedSubject = new BYTE[cbEncodedSubject];
|
|
if (pbEncodedSubject == NULL) goto ErrorReturn;
|
|
|
|
if(!CertStrToNameW(
|
|
X509_ASN_ENCODING,
|
|
g_wszSubjectX500Name,
|
|
0,
|
|
NULL,
|
|
pbEncodedSubject,
|
|
&cbEncodedSubject,
|
|
NULL))
|
|
{
|
|
ERROR_OUT(("CertStrToNameW(2) failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
cbEncoded=cbEncodedSubject;
|
|
pbEncoded=pbEncodedSubject;
|
|
|
|
fResult = TRUE;
|
|
goto CommonReturn;
|
|
|
|
ErrorReturn:
|
|
if (pbEncoded) {
|
|
delete (pbEncoded);
|
|
pbEncoded = NULL;
|
|
}
|
|
cbEncoded = 0;
|
|
fResult = FALSE;
|
|
CommonReturn:
|
|
*ppbEncoded = pbEncoded;
|
|
*pcbEncoded = cbEncoded;
|
|
return fResult;
|
|
}
|
|
|
|
|
|
// The test root's public key isn't encoded properly in the certificate.
|
|
// It's missing a leading zero to make it a unsigned integer.
|
|
static BYTE rgbTestRoot[] = {
|
|
#include "root.h"
|
|
};
|
|
static CERT_PUBLIC_KEY_INFO TestRootPublicKeyInfo = {
|
|
szOID_RSA_RSA, 0, NULL, sizeof(rgbTestRoot), rgbTestRoot, 0
|
|
};
|
|
|
|
static BYTE rgbTestRootInfoAsn[] = {
|
|
#include "rootasn.h"
|
|
};
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// X509 Extensions: Allocate and Encode functions
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CreateEnhancedKeyUsage(
|
|
OUT BYTE **ppbEncoded,
|
|
IN OUT DWORD *pcbEncoded
|
|
)
|
|
{
|
|
BOOL fResult = TRUE;
|
|
LPBYTE pbEncoded =NULL;
|
|
DWORD cbEncoded;
|
|
PCERT_ENHKEY_USAGE pUsage =NULL;
|
|
|
|
//
|
|
// Allocate a cert enhanced key usage structure and fill it in
|
|
//
|
|
|
|
pUsage = (PCERT_ENHKEY_USAGE) new BYTE[sizeof(CERT_ENHKEY_USAGE) +
|
|
2 * sizeof(LPSTR)];
|
|
if ( pUsage != NULL )
|
|
{
|
|
pUsage->cUsageIdentifier = 2;
|
|
pUsage->rgpszUsageIdentifier = (LPSTR *)((LPBYTE)pUsage+sizeof(CERT_ENHKEY_USAGE));
|
|
|
|
pUsage->rgpszUsageIdentifier[0] = szOID_PKIX_KP_CLIENT_AUTH;
|
|
pUsage->rgpszUsageIdentifier[1] = szOID_PKIX_KP_SERVER_AUTH;
|
|
}
|
|
else
|
|
{
|
|
fResult = FALSE;
|
|
}
|
|
|
|
//
|
|
// Encode the usage
|
|
//
|
|
|
|
if ( fResult == TRUE )
|
|
{
|
|
fResult = CryptEncodeObject(
|
|
X509_ASN_ENCODING,
|
|
szOID_ENHANCED_KEY_USAGE,
|
|
pUsage,
|
|
NULL,
|
|
&cbEncoded
|
|
);
|
|
|
|
if ( fResult == TRUE )
|
|
{
|
|
pbEncoded = new BYTE[cbEncoded];
|
|
if ( pbEncoded != NULL )
|
|
{
|
|
fResult = CryptEncodeObject(
|
|
X509_ASN_ENCODING,
|
|
szOID_ENHANCED_KEY_USAGE,
|
|
pUsage,
|
|
pbEncoded,
|
|
&cbEncoded
|
|
);
|
|
}
|
|
else
|
|
{
|
|
fResult = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Cleanup
|
|
//
|
|
|
|
delete (pUsage);
|
|
|
|
if ( fResult == TRUE )
|
|
{
|
|
*ppbEncoded = pbEncoded;
|
|
*pcbEncoded = cbEncoded;
|
|
}
|
|
else
|
|
{
|
|
delete (pbEncoded);
|
|
}
|
|
|
|
return( fResult );
|
|
}
|
|
|
|
BOOL CreateSpcCommonName(
|
|
OUT BYTE **ppbEncoded,
|
|
IN OUT DWORD *pcbEncoded
|
|
)
|
|
{
|
|
BOOL fResult;
|
|
BYTE *pbEncoded = NULL;
|
|
DWORD cbEncoded;
|
|
|
|
CERT_NAME_VALUE NameValue;
|
|
|
|
NameValue.dwValueType = CERT_RDN_UNICODE_STRING;
|
|
NameValue.Value.pbData = (BYTE *) g_wszSubjectDisplayName;
|
|
NameValue.Value.cbData =0;
|
|
|
|
cbEncoded = 0;
|
|
CryptEncodeObject(X509_ASN_ENCODING, X509_UNICODE_NAME_VALUE,
|
|
&NameValue,
|
|
NULL, // pbEncoded
|
|
&cbEncoded
|
|
);
|
|
if (cbEncoded == 0) {
|
|
ERROR_OUT(("CryptEncodeObject failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
pbEncoded = new BYTE[cbEncoded];
|
|
if (pbEncoded == NULL) goto ErrorReturn;
|
|
if (!CryptEncodeObject(X509_ASN_ENCODING, X509_UNICODE_NAME_VALUE,
|
|
&NameValue,
|
|
pbEncoded,
|
|
&cbEncoded
|
|
)) {
|
|
ERROR_OUT(("CryptEncodeObject failed: %x", GetLastError()));
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
fResult = TRUE;
|
|
goto CommonReturn;
|
|
|
|
ErrorReturn:
|
|
if (pbEncoded) {
|
|
delete (pbEncoded);
|
|
pbEncoded = NULL;
|
|
}
|
|
cbEncoded = 0;
|
|
fResult = FALSE;
|
|
CommonReturn:
|
|
*ppbEncoded = pbEncoded;
|
|
*pcbEncoded = cbEncoded;
|
|
return fResult;
|
|
}
|
|
|
|
|