549 lines
16 KiB
C++
549 lines
16 KiB
C++
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
//
|
||
|
// Copyright (C) Microsoft Corporation, 1996 - 1999
|
||
|
//
|
||
|
// File: msghlpr.cpp
|
||
|
//
|
||
|
// Contents: Cryptographic Message Helper APIs
|
||
|
//
|
||
|
// APIs: CryptMsgGetAndVerifySigner
|
||
|
// CryptMsgSignCTL
|
||
|
// CryptMsgEncodeAndSignCTL
|
||
|
//
|
||
|
// History: 02-May-97 philh created
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
#include "global.hxx"
|
||
|
#include "pkialloc.h"
|
||
|
|
||
|
void *ICM_AllocAndGetMsgParam(
|
||
|
IN HCRYPTMSG hMsg,
|
||
|
IN DWORD dwParamType,
|
||
|
IN DWORD dwIndex
|
||
|
)
|
||
|
{
|
||
|
void *pvData;
|
||
|
DWORD cbData;
|
||
|
|
||
|
if (!CryptMsgGetParam(
|
||
|
hMsg,
|
||
|
dwParamType,
|
||
|
dwIndex,
|
||
|
NULL, // pvData
|
||
|
&cbData) || 0 == cbData)
|
||
|
goto GetParamError;
|
||
|
if (NULL == (pvData = ICM_Alloc(cbData)))
|
||
|
goto OutOfMemory;
|
||
|
if (!CryptMsgGetParam(
|
||
|
hMsg,
|
||
|
dwParamType,
|
||
|
dwIndex,
|
||
|
pvData,
|
||
|
&cbData)) {
|
||
|
ICM_Free(pvData);
|
||
|
goto GetParamError;
|
||
|
}
|
||
|
|
||
|
CommonReturn:
|
||
|
return pvData;
|
||
|
ErrorReturn:
|
||
|
pvData = NULL;
|
||
|
goto CommonReturn;
|
||
|
TRACE_ERROR(OutOfMemory)
|
||
|
TRACE_ERROR(GetParamError)
|
||
|
}
|
||
|
|
||
|
#ifdef CMS_PKCS7
|
||
|
|
||
|
BOOL ICM_GetAndVerifySigner(
|
||
|
IN HCRYPTMSG hCryptMsg,
|
||
|
IN DWORD dwSignerIndex,
|
||
|
IN DWORD cSignerStore,
|
||
|
IN OPTIONAL HCERTSTORE *rghSignerStore,
|
||
|
IN DWORD dwFlags,
|
||
|
OUT OPTIONAL PCCERT_CONTEXT *ppSigner
|
||
|
)
|
||
|
{
|
||
|
BOOL fResult;
|
||
|
PCERT_INFO pSignerId = NULL;
|
||
|
PCCERT_CONTEXT pSigner = NULL;
|
||
|
DWORD dwCertEncodingType;
|
||
|
DWORD dwVerifyErr = 0;
|
||
|
HCERTSTORE hCollection = NULL;
|
||
|
|
||
|
if (NULL == (pSignerId = (PCERT_INFO) ICM_AllocAndGetMsgParam(
|
||
|
hCryptMsg,
|
||
|
CMSG_SIGNER_CERT_INFO_PARAM,
|
||
|
dwSignerIndex
|
||
|
))) goto GetSignerError;
|
||
|
|
||
|
// If no CertEncodingType, then, use the MsgEncodingType
|
||
|
dwCertEncodingType = ((PCRYPT_MSG_INFO) hCryptMsg)->dwEncodingType;
|
||
|
if (0 == (dwCertEncodingType & CERT_ENCODING_TYPE_MASK))
|
||
|
dwCertEncodingType =
|
||
|
(dwCertEncodingType >> 16) & CERT_ENCODING_TYPE_MASK;
|
||
|
|
||
|
|
||
|
if (NULL == (hCollection = CertOpenStore(
|
||
|
CERT_STORE_PROV_COLLECTION,
|
||
|
0, // dwEncodingType
|
||
|
0, // hCryptProv
|
||
|
0, // dwFlags
|
||
|
NULL // pvPara
|
||
|
)))
|
||
|
goto OpenCollectionStoreError;
|
||
|
|
||
|
if (0 == (dwFlags & CMSG_TRUSTED_SIGNER_FLAG)) {
|
||
|
HCERTSTORE hMsgCertStore;
|
||
|
|
||
|
// Open a cert store initialized with certs from the message
|
||
|
// and add to collection
|
||
|
if (hMsgCertStore = CertOpenStore(
|
||
|
CERT_STORE_PROV_MSG,
|
||
|
dwCertEncodingType,
|
||
|
0, // hCryptProv
|
||
|
0, // dwFlags
|
||
|
hCryptMsg // pvPara
|
||
|
)) {
|
||
|
CertAddStoreToCollection(
|
||
|
hCollection,
|
||
|
hMsgCertStore,
|
||
|
CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG,
|
||
|
0 // dwPriority
|
||
|
);
|
||
|
CertCloseStore(hMsgCertStore, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add all the signer stores to the collection
|
||
|
for ( ; cSignerStore > 0; cSignerStore--, rghSignerStore++) {
|
||
|
HCERTSTORE hSignerStore = *rghSignerStore;
|
||
|
if (NULL == hSignerStore)
|
||
|
continue;
|
||
|
|
||
|
CertAddStoreToCollection(
|
||
|
hCollection,
|
||
|
hSignerStore,
|
||
|
CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG,
|
||
|
0 // dwPriority
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (pSigner = CertGetSubjectCertificateFromStore(hCollection,
|
||
|
dwCertEncodingType, pSignerId)) {
|
||
|
CMSG_CTRL_VERIFY_SIGNATURE_EX_PARA CtrlPara;
|
||
|
|
||
|
if (dwFlags & CMSG_SIGNER_ONLY_FLAG)
|
||
|
goto SuccessReturn;
|
||
|
|
||
|
memset(&CtrlPara, 0, sizeof(CtrlPara));
|
||
|
CtrlPara.cbSize = sizeof(CtrlPara);
|
||
|
// CtrlPara.hCryptProv =
|
||
|
CtrlPara.dwSignerIndex = dwSignerIndex;
|
||
|
CtrlPara.dwSignerType = CMSG_VERIFY_SIGNER_CERT;
|
||
|
CtrlPara.pvSigner = (void *) pSigner;
|
||
|
|
||
|
if (CryptMsgControl(
|
||
|
hCryptMsg,
|
||
|
0, // dwFlags
|
||
|
CMSG_CTRL_VERIFY_SIGNATURE_EX,
|
||
|
&CtrlPara)) goto SuccessReturn;
|
||
|
else {
|
||
|
dwVerifyErr = GetLastError();
|
||
|
|
||
|
if (CRYPT_E_MISSING_PUBKEY_PARA == dwVerifyErr) {
|
||
|
PCCERT_CHAIN_CONTEXT pChainContext;
|
||
|
CERT_CHAIN_PARA ChainPara;
|
||
|
|
||
|
// Build a chain. Hopefully, the signer inherit's its public key
|
||
|
// parameters from up the chain
|
||
|
|
||
|
memset(&ChainPara, 0, sizeof(ChainPara));
|
||
|
ChainPara.cbSize = sizeof(ChainPara);
|
||
|
if (CertGetCertificateChain(
|
||
|
NULL, // hChainEngine
|
||
|
pSigner,
|
||
|
NULL, // pTime
|
||
|
hCollection,
|
||
|
&ChainPara,
|
||
|
CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL,
|
||
|
NULL, // pvReserved
|
||
|
&pChainContext
|
||
|
))
|
||
|
CertFreeCertificateChain(pChainContext);
|
||
|
|
||
|
|
||
|
// Try again. Hopefully the above chain building updated the
|
||
|
// signer's context property with the missing public key
|
||
|
// parameters
|
||
|
if (CryptMsgControl(
|
||
|
hCryptMsg,
|
||
|
0, // dwFlags
|
||
|
CMSG_CTRL_VERIFY_SIGNATURE_EX,
|
||
|
&CtrlPara)) goto SuccessReturn;
|
||
|
}
|
||
|
}
|
||
|
CertFreeCertificateContext(pSigner);
|
||
|
pSigner = NULL;
|
||
|
}
|
||
|
|
||
|
if (dwVerifyErr)
|
||
|
goto VerifySignatureError;
|
||
|
else
|
||
|
goto NoSignerError;
|
||
|
|
||
|
SuccessReturn:
|
||
|
fResult = TRUE;
|
||
|
CommonReturn:
|
||
|
if (hCollection)
|
||
|
CertCloseStore(hCollection, 0);
|
||
|
ICM_Free(pSignerId);
|
||
|
if (ppSigner)
|
||
|
*ppSigner = pSigner;
|
||
|
else if (pSigner)
|
||
|
CertFreeCertificateContext(pSigner);
|
||
|
return fResult;
|
||
|
ErrorReturn:
|
||
|
fResult = FALSE;
|
||
|
goto CommonReturn;
|
||
|
|
||
|
TRACE_ERROR(OpenCollectionStoreError)
|
||
|
TRACE_ERROR(GetSignerError)
|
||
|
SET_ERROR(NoSignerError, CRYPT_E_NO_TRUSTED_SIGNER)
|
||
|
SET_ERROR_VAR(VerifySignatureError, dwVerifyErr)
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
BOOL ICM_GetAndVerifySigner(
|
||
|
IN HCRYPTMSG hCryptMsg,
|
||
|
IN DWORD dwSignerIndex,
|
||
|
IN DWORD cSignerStore,
|
||
|
IN OPTIONAL HCERTSTORE *rghSignerStore,
|
||
|
IN DWORD dwFlags,
|
||
|
OUT OPTIONAL PCCERT_CONTEXT *ppSigner
|
||
|
)
|
||
|
{
|
||
|
BOOL fResult;
|
||
|
PCERT_INFO pSignerId = NULL;
|
||
|
PCCERT_CONTEXT pSigner = NULL;
|
||
|
DWORD dwCertEncodingType;
|
||
|
DWORD dwVerifyErr = 0;
|
||
|
|
||
|
if (NULL == (pSignerId = (PCERT_INFO) ICM_AllocAndGetMsgParam(
|
||
|
hCryptMsg,
|
||
|
CMSG_SIGNER_CERT_INFO_PARAM,
|
||
|
dwSignerIndex
|
||
|
))) goto GetSignerError;
|
||
|
|
||
|
// If no CertEncodingType, then, use the MsgEncodingType
|
||
|
dwCertEncodingType = ((PCRYPT_MSG_INFO) hCryptMsg)->dwEncodingType;
|
||
|
if (0 == (dwCertEncodingType & CERT_ENCODING_TYPE_MASK))
|
||
|
dwCertEncodingType =
|
||
|
(dwCertEncodingType >> 16) & CERT_ENCODING_TYPE_MASK;
|
||
|
|
||
|
if (0 == (dwFlags & CMSG_TRUSTED_SIGNER_FLAG)) {
|
||
|
HCERTSTORE hMsgCertStore;
|
||
|
|
||
|
// Open a cert store initialized with certs from the message
|
||
|
if (hMsgCertStore = CertOpenStore(
|
||
|
CERT_STORE_PROV_MSG,
|
||
|
dwCertEncodingType,
|
||
|
0, // hCryptProv
|
||
|
0, // dwFlags
|
||
|
hCryptMsg // pvPara
|
||
|
)) {
|
||
|
pSigner = CertGetSubjectCertificateFromStore(hMsgCertStore,
|
||
|
dwCertEncodingType, pSignerId);
|
||
|
CertCloseStore(hMsgCertStore, 0);
|
||
|
}
|
||
|
|
||
|
if (pSigner) {
|
||
|
if (dwFlags & CMSG_SIGNER_ONLY_FLAG)
|
||
|
goto SuccessReturn;
|
||
|
if (CryptMsgControl(
|
||
|
hCryptMsg,
|
||
|
0, // dwFlags
|
||
|
CMSG_CTRL_VERIFY_SIGNATURE,
|
||
|
pSigner->pCertInfo)) goto SuccessReturn;
|
||
|
else
|
||
|
dwVerifyErr = GetLastError();
|
||
|
CertFreeCertificateContext(pSigner);
|
||
|
pSigner = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for ( ; cSignerStore > 0; cSignerStore--, rghSignerStore++) {
|
||
|
HCERTSTORE hSignerStore = *rghSignerStore;
|
||
|
if (NULL == hSignerStore)
|
||
|
continue;
|
||
|
if (pSigner = CertGetSubjectCertificateFromStore(hSignerStore,
|
||
|
dwCertEncodingType, pSignerId)) {
|
||
|
if (dwFlags & CMSG_SIGNER_ONLY_FLAG)
|
||
|
goto SuccessReturn;
|
||
|
if (CryptMsgControl(
|
||
|
hCryptMsg,
|
||
|
0, // dwFlags
|
||
|
CMSG_CTRL_VERIFY_SIGNATURE,
|
||
|
pSigner->pCertInfo)) goto SuccessReturn;
|
||
|
else
|
||
|
dwVerifyErr = GetLastError();
|
||
|
CertFreeCertificateContext(pSigner);
|
||
|
pSigner = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dwVerifyErr)
|
||
|
goto VerifySignatureError;
|
||
|
else
|
||
|
goto NoSignerError;
|
||
|
|
||
|
SuccessReturn:
|
||
|
fResult = TRUE;
|
||
|
CommonReturn:
|
||
|
ICM_Free(pSignerId);
|
||
|
if (ppSigner)
|
||
|
*ppSigner = pSigner;
|
||
|
else if (pSigner)
|
||
|
CertFreeCertificateContext(pSigner);
|
||
|
return fResult;
|
||
|
ErrorReturn:
|
||
|
fResult = FALSE;
|
||
|
goto CommonReturn;
|
||
|
|
||
|
TRACE_ERROR(GetSignerError)
|
||
|
SET_ERROR(NoSignerError, CRYPT_E_NO_TRUSTED_SIGNER)
|
||
|
SET_ERROR_VAR(VerifySignatureError, dwVerifyErr)
|
||
|
}
|
||
|
|
||
|
#endif // CMS_PKCS7
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
// Get and verify the signer of a cryptographic message.
|
||
|
//
|
||
|
// If CMSG_TRUSTED_SIGNER_FLAG is set, then, treat the Signer stores as being
|
||
|
// trusted and only search them to find the certificate corresponding to the
|
||
|
// signer's issuer and serial number. Otherwise, the SignerStores are
|
||
|
// optionally provided to supplement the message's store of certificates.
|
||
|
// If a signer certificate is found, its public key is used to verify
|
||
|
// the message signature. The CMSG_SIGNER_ONLY_FLAG can be set to
|
||
|
// return the signer without doing the signature verify.
|
||
|
//
|
||
|
// If CMSG_USE_SIGNER_INDEX_FLAG is set, then, only get the signer specified
|
||
|
// by *pdwSignerIndex. Otherwise, iterate through all the signers
|
||
|
// until a signer verifies or no more signers.
|
||
|
//
|
||
|
// For a verified signature, *ppSigner is updated with certificate context
|
||
|
// of the signer and *pdwSignerIndex is updated with the index of the signer.
|
||
|
// ppSigner and/or pdwSignerIndex can be NULL, indicating the caller isn't
|
||
|
// interested in getting the CertContext and/or index of the signer.
|
||
|
//--------------------------------------------------------------------------
|
||
|
BOOL
|
||
|
WINAPI
|
||
|
CryptMsgGetAndVerifySigner(
|
||
|
IN HCRYPTMSG hCryptMsg,
|
||
|
IN DWORD cSignerStore,
|
||
|
IN OPTIONAL HCERTSTORE *rghSignerStore,
|
||
|
IN DWORD dwFlags,
|
||
|
OUT OPTIONAL PCCERT_CONTEXT *ppSigner,
|
||
|
IN OUT OPTIONAL DWORD *pdwSignerIndex
|
||
|
)
|
||
|
{
|
||
|
BOOL fResult = FALSE;
|
||
|
DWORD dwSignerCount;
|
||
|
DWORD dwSignerIndex;
|
||
|
|
||
|
if (dwFlags & CMSG_USE_SIGNER_INDEX_FLAG) {
|
||
|
dwSignerCount = 1;
|
||
|
dwSignerIndex = *pdwSignerIndex;
|
||
|
} else {
|
||
|
DWORD cbData;
|
||
|
|
||
|
dwSignerIndex = 0;
|
||
|
if (pdwSignerIndex)
|
||
|
*pdwSignerIndex = 0;
|
||
|
cbData = sizeof(dwSignerCount);
|
||
|
if (!CryptMsgGetParam(
|
||
|
hCryptMsg,
|
||
|
CMSG_SIGNER_COUNT_PARAM,
|
||
|
0, // dwIndex
|
||
|
&dwSignerCount,
|
||
|
&cbData) || 0 == dwSignerCount)
|
||
|
goto NoSignerError;
|
||
|
}
|
||
|
|
||
|
// Minimum of one iteration
|
||
|
for ( ; dwSignerCount > 0; dwSignerCount--, dwSignerIndex++) {
|
||
|
if (fResult = ICM_GetAndVerifySigner(
|
||
|
hCryptMsg,
|
||
|
dwSignerIndex,
|
||
|
cSignerStore,
|
||
|
rghSignerStore,
|
||
|
dwFlags,
|
||
|
ppSigner)) {
|
||
|
if (pdwSignerIndex && 0 == (dwFlags & CMSG_USE_SIGNER_INDEX_FLAG))
|
||
|
*pdwSignerIndex = dwSignerIndex;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CommonReturn:
|
||
|
return fResult;
|
||
|
ErrorReturn:
|
||
|
if (ppSigner)
|
||
|
*ppSigner = NULL;
|
||
|
fResult = FALSE;
|
||
|
goto CommonReturn;
|
||
|
|
||
|
SET_ERROR(NoSignerError, CRYPT_E_NO_TRUSTED_SIGNER)
|
||
|
}
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
// Sign an encoded CTL.
|
||
|
//--------------------------------------------------------------------------
|
||
|
BOOL
|
||
|
WINAPI
|
||
|
CryptMsgSignCTL(
|
||
|
IN DWORD dwMsgEncodingType,
|
||
|
IN BYTE *pbCtlContent,
|
||
|
IN DWORD cbCtlContent,
|
||
|
IN PCMSG_SIGNED_ENCODE_INFO pSignInfo,
|
||
|
IN DWORD dwFlags,
|
||
|
OUT BYTE *pbEncoded,
|
||
|
IN OUT DWORD *pcbEncoded
|
||
|
)
|
||
|
{
|
||
|
BOOL fResult;
|
||
|
HCRYPTMSG hMsg = NULL;
|
||
|
|
||
|
DWORD dwMsgFlags;
|
||
|
|
||
|
#ifdef CMS_PKCS7
|
||
|
if (dwFlags & CMSG_CMS_ENCAPSULATED_CTL_FLAG)
|
||
|
dwMsgFlags = CMSG_CMS_ENCAPSULATED_CONTENT_FLAG;
|
||
|
else
|
||
|
dwMsgFlags = 0;
|
||
|
#else
|
||
|
dwMsgFlags = 0;
|
||
|
#endif // CMS_PKCS7
|
||
|
|
||
|
if (NULL == pbEncoded) {
|
||
|
if (0 == (*pcbEncoded = CryptMsgCalculateEncodedLength(
|
||
|
dwMsgEncodingType,
|
||
|
dwMsgFlags,
|
||
|
CMSG_SIGNED,
|
||
|
pSignInfo,
|
||
|
szOID_CTL,
|
||
|
cbCtlContent))) goto CalculateEncodedLengthError;
|
||
|
fResult = TRUE;
|
||
|
} else {
|
||
|
if (NULL == (hMsg = CryptMsgOpenToEncode(
|
||
|
dwMsgEncodingType,
|
||
|
dwMsgFlags,
|
||
|
CMSG_SIGNED,
|
||
|
pSignInfo,
|
||
|
szOID_CTL,
|
||
|
NULL // pStreamInfo
|
||
|
))) goto OpenToEncodeError;
|
||
|
if (!CryptMsgUpdate(
|
||
|
hMsg,
|
||
|
pbCtlContent,
|
||
|
cbCtlContent,
|
||
|
TRUE // fFinal
|
||
|
)) goto UpdateError;
|
||
|
|
||
|
fResult = CryptMsgGetParam(
|
||
|
hMsg,
|
||
|
CMSG_CONTENT_PARAM,
|
||
|
0, // dwIndex
|
||
|
pbEncoded,
|
||
|
pcbEncoded);
|
||
|
}
|
||
|
|
||
|
CommonReturn:
|
||
|
if (hMsg)
|
||
|
CryptMsgClose(hMsg);
|
||
|
return fResult;
|
||
|
ErrorReturn:
|
||
|
*pcbEncoded = 0;
|
||
|
fResult = FALSE;
|
||
|
goto CommonReturn;
|
||
|
|
||
|
TRACE_ERROR(CalculateEncodedLengthError)
|
||
|
TRACE_ERROR(OpenToEncodeError)
|
||
|
TRACE_ERROR(UpdateError)
|
||
|
}
|
||
|
|
||
|
|
||
|
//+-------------------------------------------------------------------------
|
||
|
// Encode the CTL and create a signed message containing the encoded CTL.
|
||
|
//--------------------------------------------------------------------------
|
||
|
BOOL
|
||
|
WINAPI
|
||
|
CryptMsgEncodeAndSignCTL(
|
||
|
IN DWORD dwMsgEncodingType,
|
||
|
IN PCTL_INFO pCtlInfo,
|
||
|
IN PCMSG_SIGNED_ENCODE_INFO pSignInfo,
|
||
|
IN DWORD dwFlags,
|
||
|
OUT BYTE *pbEncoded,
|
||
|
IN OUT DWORD *pcbEncoded
|
||
|
)
|
||
|
{
|
||
|
BOOL fResult;
|
||
|
BYTE *pbContent = NULL;
|
||
|
DWORD cbContent;
|
||
|
DWORD dwEncodingType;
|
||
|
LPCSTR lpszStructType;
|
||
|
DWORD dwEncodeFlags;
|
||
|
|
||
|
dwEncodingType = (dwMsgEncodingType >> 16) & CERT_ENCODING_TYPE_MASK;
|
||
|
assert(dwEncodingType != 0);
|
||
|
if (0 == dwEncodingType)
|
||
|
goto InvalidArg;
|
||
|
|
||
|
dwEncodeFlags = CRYPT_ENCODE_ALLOC_FLAG;
|
||
|
if (dwFlags & CMSG_ENCODE_SORTED_CTL_FLAG) {
|
||
|
lpszStructType = PKCS_SORTED_CTL;
|
||
|
if (dwFlags & CMSG_ENCODE_HASHED_SUBJECT_IDENTIFIER_FLAG)
|
||
|
dwEncodeFlags |=
|
||
|
CRYPT_SORTED_CTL_ENCODE_HASHED_SUBJECT_IDENTIFIER_FLAG;
|
||
|
} else {
|
||
|
lpszStructType = PKCS_CTL;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (!CryptEncodeObjectEx(
|
||
|
dwEncodingType,
|
||
|
lpszStructType,
|
||
|
pCtlInfo,
|
||
|
dwEncodeFlags,
|
||
|
&PkiEncodePara,
|
||
|
(void *) &pbContent,
|
||
|
&cbContent
|
||
|
)) goto EncodeError;
|
||
|
|
||
|
fResult = CryptMsgSignCTL(
|
||
|
dwMsgEncodingType,
|
||
|
pbContent,
|
||
|
cbContent,
|
||
|
pSignInfo,
|
||
|
dwFlags,
|
||
|
pbEncoded,
|
||
|
pcbEncoded
|
||
|
);
|
||
|
|
||
|
CommonReturn:
|
||
|
PkiFree(pbContent);
|
||
|
return fResult;
|
||
|
|
||
|
ErrorReturn:
|
||
|
*pcbEncoded = 0;
|
||
|
fResult = FALSE;
|
||
|
goto CommonReturn;
|
||
|
|
||
|
SET_ERROR(InvalidArg, E_INVALIDARG)
|
||
|
TRACE_ERROR(EncodeError)
|
||
|
}
|