[crypt32] CryptProtectData/CryptUnprotectData helper functions
Kees Cook
kees at outflux.net
Wed May 18 07:49:01 CDT 2005
This implements a series of serialization/unserialization routines to
convert the opaque data blob into and out of the flat byte array into
the "protect_data_t" structure and all it's DATA_BLOBs.
Also included are a number of debug reporting functions used to display
arguments sent to the CryptProtectData/CryptUnprotectData functions.
ChangeLog:
Implement CryptProtectData/CryptUnprotectData helper functions.
--
Kees Cook @outflux.net
-------------- next part --------------
--- dlls/crypt32/protectdata.c.orig 2005-05-18 05:42:28.527366687 -0700
+++ dlls/crypt32/protectdata.c 2005-05-18 05:46:32.123689788 -0700
@@ -103,3 +103,695 @@
DATA_BLOB fingerprint;
};
+/* this is used to check if an incoming structure was built by Wine */
+static const char * crypt_magic_str = "Wine Crypt32 ok";
+
+/* debugging tool to print strings of hex chars */
+static int
+hexprint(const char *s, unsigned char *p, int n)
+{
+ char report[80];
+ int r=-1;
+ snprintf(report,16,"%14s:", s);
+ while (--n >= 0)
+ {
+ if (r++ % 20 == 19)
+ {
+ wine_dbg_printf("%s\n",report);
+ snprintf(report,16,"%14s ", "");
+ }
+ sprintf(report+strlen(report)," %02x", *p++);
+ }
+ wine_dbg_printf("%s\n",report);
+ return 0;
+}
+
+static
+void serialize_dword(DWORD value,BYTE ** ptr)
+{
+ /*TRACE("called\n");*/
+
+ *((DWORD*)*ptr)=value;
+ *ptr+=sizeof(DWORD);
+}
+
+static
+void serialize_string(BYTE * str,BYTE ** ptr,DWORD len, DWORD width,
+ BOOL prepend_len)
+{
+ /*TRACE("called %ux%u\n",(unsigned int)len,(unsigned int)width);*/
+
+ if (prepend_len)
+ {
+ serialize_dword(len,ptr);
+ }
+ memcpy(*ptr,str,len*width);
+ *ptr+=len*width;
+}
+
+static
+BOOL unserialize_dword(BYTE * ptr, DWORD *index, DWORD size, DWORD * value)
+{
+ DWORD * pValue;
+
+ /*TRACE("called\n");*/
+
+ if (!ptr || !index || !value) return FALSE;
+
+ if (*index+sizeof(DWORD)>size)
+ {
+ return FALSE;
+ }
+
+ pValue=(DWORD*)&(ptr[*index]);
+ *value=*pValue;
+
+ *index+=sizeof(DWORD);
+
+ return TRUE;
+}
+
+static
+BOOL unserialize_string(BYTE * ptr, DWORD *index, DWORD size,
+ DWORD len, DWORD width, BOOL inline_len,
+ BYTE ** data, DWORD * stored)
+{
+ /*TRACE("called\n");*/
+
+ if (!ptr || !data) return FALSE;
+
+ if (inline_len) {
+ if (!unserialize_dword(ptr,index,size,&len))
+ return FALSE;
+ }
+
+ if (*index+len*width>size)
+ {
+ return FALSE;
+ }
+
+ if (!(*data = HeapAlloc( GetProcessHeap(), 0, len*width)))
+ {
+ return FALSE;
+ }
+
+ memcpy(*data,&(ptr[*index]),len*width);
+ if (stored)
+ {
+ *stored = len;
+ }
+ *index+=len*width;
+
+ return TRUE;
+}
+
+static
+BOOL serialize(struct protect_data_t * pInfo, DATA_BLOB * pSerial)
+{
+ BYTE * ptr;
+ DWORD dwStrLen;
+ DWORD dwStruct;
+
+ TRACE("called\n");
+
+ if (!pInfo || !pInfo->szDataDescr || !pSerial ||
+ !pInfo->info0.pbData || !pInfo->info1.pbData ||
+ !pInfo->data0.pbData || !pInfo->salt.pbData ||
+ !pInfo->cipher.pbData || !pInfo->fingerprint.pbData)
+ {
+ return FALSE;
+ }
+
+ if (pInfo->info0.cbData!=16)
+ {
+ ERR("protect_data_t info0 not 16 bytes long\n");
+ }
+
+ if (pInfo->info1.cbData!=16)
+ {
+ ERR("protect_data_t info1 not 16 bytes long\n");
+ }
+
+ dwStrLen=lstrlenW(pInfo->szDataDescr);
+
+ pSerial->cbData=0;
+ pSerial->cbData+=sizeof(DWORD)*8; /* 8 raw DWORDs */
+ pSerial->cbData+=sizeof(DWORD)*4; /* 4 BLOBs with size */
+ pSerial->cbData+=pInfo->info0.cbData;
+ pSerial->cbData+=pInfo->info1.cbData;
+ pSerial->cbData+=(dwStrLen+1)*sizeof(WCHAR) + 4; /* str, null, size */
+ pSerial->cbData+=pInfo->data0.cbData;
+ pSerial->cbData+=pInfo->salt.cbData;
+ pSerial->cbData+=pInfo->cipher.cbData;
+ pSerial->cbData+=pInfo->fingerprint.cbData;
+
+ /* save the actual structure size */
+ dwStruct = pSerial->cbData;
+ /* There may be a 256 byte minimum, but I can't prove it. */
+ /*if (pSerial->cbData<256) pSerial->cbData=256;*/
+
+ pSerial->pbData=LocalAlloc(LPTR,pSerial->cbData);
+ if (!pSerial->pbData) return FALSE;
+
+ ptr=pSerial->pbData;
+
+ /* count0 */
+ serialize_dword(pInfo->count0,&ptr);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* info0 */
+ serialize_string(pInfo->info0.pbData,&ptr,
+ pInfo->info0.cbData,sizeof(BYTE),FALSE);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* count1 */
+ serialize_dword(pInfo->count1,&ptr);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* info1 */
+ serialize_string(pInfo->info1.pbData,&ptr,
+ pInfo->info1.cbData,sizeof(BYTE),FALSE);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* null0 */
+ serialize_dword(pInfo->null0,&ptr);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* szDataDescr */
+ serialize_string((BYTE*)pInfo->szDataDescr,&ptr,
+ (dwStrLen+1)*sizeof(WCHAR),sizeof(BYTE),TRUE);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* unknown0 */
+ serialize_dword(pInfo->unknown0,&ptr);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+ /* unknown1 */
+ serialize_dword(pInfo->unknown1,&ptr);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* data0 */
+ serialize_string(pInfo->data0.pbData,&ptr,
+ pInfo->data0.cbData,sizeof(BYTE),TRUE);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* null1 */
+ serialize_dword(pInfo->null1,&ptr);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* unknown2 */
+ serialize_dword(pInfo->unknown2,&ptr);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+ /* unknown3 */
+ serialize_dword(pInfo->unknown3,&ptr);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* salt */
+ serialize_string(pInfo->salt.pbData,&ptr,
+ pInfo->salt.cbData,sizeof(BYTE),TRUE);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* cipher */
+ serialize_string(pInfo->cipher.pbData,&ptr,
+ pInfo->cipher.cbData,sizeof(BYTE),TRUE);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ /* fingerprint */
+ serialize_string(pInfo->fingerprint.pbData,&ptr,
+ pInfo->fingerprint.cbData,sizeof(BYTE),TRUE);
+ /*TRACE("used %u\n",ptr-pSerial->pbData);*/
+
+ if (ptr - pSerial->pbData != dwStruct)
+ {
+ ERR("struct size changed!? %u != expected %u\n",
+ ptr - pSerial->pbData, (unsigned int)dwStruct);
+ LocalFree(pSerial->pbData);
+ pSerial->pbData=NULL;
+ pSerial->cbData=0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static
+BOOL unserialize(DATA_BLOB * pSerial, struct protect_data_t * pInfo)
+{
+ BYTE * ptr;
+ DWORD index;
+ DWORD size;
+ BOOL status=TRUE;
+
+ TRACE("called\n");
+
+ if (!pInfo || !pSerial || !pSerial->pbData)
+ return FALSE;
+
+ index=0;
+ ptr=pSerial->pbData;
+ size=pSerial->cbData;
+
+ /* count0 */
+ if (!unserialize_dword(ptr,&index,size,&pInfo->count0))
+ {
+ ERR("reading count0 failed!\n");
+ return FALSE;
+ }
+
+ /* info0 */
+ if (!unserialize_string(ptr,&index,size,16,sizeof(BYTE),FALSE,
+ &pInfo->info0.pbData, &pInfo->info0.cbData))
+ {
+ ERR("reading info0 failed!\n");
+ return FALSE;
+ }
+
+ /* count1 */
+ if (!unserialize_dword(ptr,&index,size,&pInfo->count1))
+ {
+ ERR("reading count1 failed!\n");
+ return FALSE;
+ }
+
+ /* info1 */
+ if (!unserialize_string(ptr,&index,size,16,sizeof(BYTE),FALSE,
+ &pInfo->info1.pbData, &pInfo->info1.cbData))
+ {
+ ERR("reading info1 failed!\n");
+ return FALSE;
+ }
+
+ /* null0 */
+ if (!unserialize_dword(ptr,&index,size,&pInfo->null0))
+ {
+ ERR("reading null0 failed!\n");
+ return FALSE;
+ }
+
+ /* szDataDescr */
+ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE,
+ (BYTE**)&pInfo->szDataDescr, NULL))
+ {
+ ERR("reading szDataDescr failed!\n");
+ return FALSE;
+ }
+
+ /* unknown0 */
+ if (!unserialize_dword(ptr,&index,size,&pInfo->unknown0))
+ {
+ ERR("reading unknown0 failed!\n");
+ return FALSE;
+ }
+
+ /* unknown1 */
+ if (!unserialize_dword(ptr,&index,size,&pInfo->unknown1))
+ {
+ ERR("reading unknown1 failed!\n");
+ return FALSE;
+ }
+
+ /* data0 */
+ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE,
+ &pInfo->data0.pbData, &pInfo->data0.cbData))
+ {
+ ERR("reading data0 failed!\n");
+ return FALSE;
+ }
+
+ /* null1 */
+ if (!unserialize_dword(ptr,&index,size,&pInfo->null1))
+ {
+ ERR("reading null1 failed!\n");
+ return FALSE;
+ }
+
+ /* unknown2 */
+ if (!unserialize_dword(ptr,&index,size,&pInfo->unknown2))
+ {
+ ERR("reading unknown2 failed!\n");
+ return FALSE;
+ }
+
+ /* unknown3 */
+ if (!unserialize_dword(ptr,&index,size,&pInfo->unknown3))
+ {
+ ERR("reading unknown3 failed!\n");
+ return FALSE;
+ }
+
+ /* salt */
+ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE,
+ &pInfo->salt.pbData, &pInfo->salt.cbData))
+ {
+ ERR("reading salt failed!\n");
+ return FALSE;
+ }
+
+ /* cipher */
+ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE,
+ &pInfo->cipher.pbData, &pInfo->cipher.cbData))
+ {
+ ERR("reading cipher failed!\n");
+ return FALSE;
+ }
+
+ /* fingerprint */
+ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE,
+ &pInfo->fingerprint.pbData, &pInfo->fingerprint.cbData))
+ {
+ ERR("reading fingerprint failed!\n");
+ return FALSE;
+ }
+
+ /* allow structure size to be too big (since some applications
+ * will pad this up to 256 bytes, it seems) */
+ if (index>size)
+ {
+ /* this is an impossible-to-reach test, but if the padding
+ * issue is ever understood, this may become more useful */
+ ERR("loaded corrupt structure! (used %u expected %u)\n",
+ (unsigned int)index, (unsigned int)size);
+ status=FALSE;
+ }
+
+ return status;
+}
+
+/* perform sanity checks */
+static
+BOOL valid_protect_data(struct protect_data_t * pInfo)
+{
+ BOOL status=TRUE;
+
+ TRACE("called\n");
+
+ if (pInfo->count0 != 0x0001)
+ {
+ ERR("count0 != 0x0001 !\n");
+ status=FALSE;
+ }
+ if (pInfo->count1 != 0x0001)
+ {
+ ERR("count0 != 0x0001 !\n");
+ status=FALSE;
+ }
+ if (pInfo->null0 != 0x0000)
+ {
+ ERR("null0 != 0x0000 !\n");
+ status=FALSE;
+ }
+ if (pInfo->null1 != 0x0000)
+ {
+ ERR("null1 != 0x0000 !\n");
+ status=FALSE;
+ }
+ /* since we have no idea what info0 is used for, and it seems
+ * rather constant, we can test for a Wine-specific magic string
+ * there to be reasonably sure we're using data created by the Wine
+ * implementation of CryptProtectData.
+ */
+ if (pInfo->info0.cbData!=strlen(crypt_magic_str)+1 ||
+ strcmp(pInfo->info0.pbData,crypt_magic_str) != 0)
+ {
+ ERR("info0 magic value not matched !\n");
+ status=FALSE;
+ }
+
+ if (!status)
+ {
+ ERR("unrecognized CryptProtectData block\n");
+ }
+
+ return status;
+}
+
+static
+void free_protect_data(struct protect_data_t * pInfo)
+{
+ TRACE("called\n");
+
+ if (!pInfo) return;
+
+ if (pInfo->info0.pbData)
+ HeapFree( GetProcessHeap(), 0, pInfo->info0.pbData);
+ if (pInfo->info1.pbData)
+ HeapFree( GetProcessHeap(), 0, pInfo->info1.pbData);
+ if (pInfo->szDataDescr)
+ HeapFree( GetProcessHeap(), 0, pInfo->szDataDescr);
+ if (pInfo->data0.pbData)
+ HeapFree( GetProcessHeap(), 0, pInfo->data0.pbData);
+ if (pInfo->salt.pbData)
+ HeapFree( GetProcessHeap(), 0, pInfo->salt.pbData);
+ if (pInfo->cipher.pbData)
+ HeapFree( GetProcessHeap(), 0, pInfo->cipher.pbData);
+ if (pInfo->fingerprint.pbData)
+ HeapFree( GetProcessHeap(), 0, pInfo->fingerprint.pbData);
+}
+
+/*
+ * Populates everything except "cipher" and "fingerprint".
+ */
+static
+BOOL fill_protect_data(struct protect_data_t * pInfo, LPCWSTR szDataDescr,
+ HCRYPTPROV hProv)
+{
+ DWORD dwStrLen;
+
+ TRACE("called\n");
+
+ if (!pInfo) return FALSE;
+
+ dwStrLen=lstrlenW(szDataDescr);
+
+ memset(pInfo,0,sizeof(*pInfo));
+
+ pInfo->count0=0x0001;
+
+ pInfo->info0.cbData=strlen(crypt_magic_str)+1;
+ pInfo->info0.pbData=strdup(crypt_magic_str);
+
+ pInfo->count1=0x0001;
+
+ pInfo->info1.cbData=strlen(crypt_magic_str)+1;
+ pInfo->info1.pbData=strdup(crypt_magic_str);
+
+ pInfo->null0=0x0000;
+
+ if ((pInfo->szDataDescr=HeapAlloc( GetProcessHeap(), 0, (dwStrLen+1)*sizeof(WCHAR))))
+ {
+ memcpy(pInfo->szDataDescr,szDataDescr,(dwStrLen+1)*sizeof(WCHAR));
+ }
+
+ pInfo->unknown0=0x0000;
+ pInfo->unknown1=0x0000;
+
+ pInfo->data0.cbData=strlen(crypt_magic_str)+1;
+ pInfo->data0.pbData=strdup(crypt_magic_str);
+
+ pInfo->null1=0x0000;
+ pInfo->unknown2=0x0000;
+ pInfo->unknown3=0x0000;
+
+ /* allocate memory to hold a salt */
+ pInfo->salt.cbData=CRYPT32_PROTECTDATA_SALT_LEN;
+ if ((pInfo->salt.pbData=HeapAlloc( GetProcessHeap(),0,pInfo->salt.cbData)))
+ {
+ /* generate random salt */
+ if (!CryptGenRandom(hProv, pInfo->salt.cbData, pInfo->salt.pbData))
+ {
+ ERR("CryptGenRandom\n");
+ free_protect_data(pInfo);
+ return FALSE;
+ }
+ }
+
+ /* debug: show our salt */
+ if (WINE_TRACE_ON(crypt))
+ {
+ wine_dbg_printf("\tsalt.cbData: %u\n",(unsigned int)pInfo->salt.cbData);
+ hexprint("pbData", pInfo->salt.pbData, pInfo->salt.cbData);
+ }
+
+ pInfo->cipher.cbData=0;
+ pInfo->cipher.pbData=NULL;
+
+ pInfo->fingerprint.cbData=0;
+ pInfo->fingerprint.pbData=NULL;
+
+ /* check all the allocations at once */
+ if (!pInfo->info0.pbData ||
+ !pInfo->info1.pbData ||
+ !pInfo->szDataDescr ||
+ !pInfo->data0.pbData ||
+ !pInfo->salt.pbData
+ )
+ {
+ ERR("could not allocate protect_data structures\n");
+ free_protect_data(pInfo);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static
+BOOL convert_hash_to_blob(HCRYPTHASH hHash, DATA_BLOB * blob)
+{
+ DWORD dwSize;
+
+ TRACE("called\n");
+
+ if (!blob) return FALSE;
+
+ dwSize=sizeof(DWORD);
+ if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&blob->cbData,
+ &dwSize, 0))
+ {
+ ERR("failed to get hash size\n");
+ return FALSE;
+ }
+
+ if (!(blob->pbData=HeapAlloc( GetProcessHeap(), 0, blob->cbData)))
+ {
+ ERR("failed to allocate blob memory\n");
+ return FALSE;
+ }
+
+ dwSize=blob->cbData;
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, blob->pbData, &dwSize, 0))
+ {
+ ERR("failed to get hash value\n");
+ HeapFree( GetProcessHeap(), 0, blob->pbData);
+ blob->pbData=NULL;
+ blob->cbData=0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* test that a given hash matches an exported-to-blob hash value */
+static
+BOOL hash_matches_blob(HCRYPTHASH hHash, DATA_BLOB * two)
+{
+ BOOL rc = FALSE;
+ DATA_BLOB one;
+
+ if (!two || !two->pbData) return FALSE;
+
+ if (!convert_hash_to_blob(hHash,&one)) {
+ return FALSE;
+ }
+
+ if ( one.cbData == two->cbData &&
+ memcmp( one.pbData, two->pbData, one.cbData ) == 0 )
+ {
+ rc = TRUE;
+ }
+
+ HeapFree( GetProcessHeap(), 0, one.pbData );
+ return rc;
+}
+
+/* create an encryption key from a given salt and optional entropy */
+static
+BOOL load_encryption_key(HCRYPTPROV hProv, DATA_BLOB * salt,
+ DATA_BLOB * pOptionalEntropy, HCRYPTKEY * phKey)
+{
+ BOOL rc = TRUE;
+ HCRYPTHASH hSaltHash;
+ char * szUsername = NULL;
+ DWORD dwUsernameLen;
+ DWORD dwError;
+
+ /* create hash for salt */
+ if (!salt || !phKey ||
+ !CryptCreateHash(hProv,CRYPT32_PROTECTDATA_HASH_CALG,0,0,&hSaltHash))
+ {
+ ERR("CryptCreateHash\n");
+ return FALSE;
+ }
+
+ /* This should be the "logon credentials" instead of username */
+ dwError=GetLastError();
+ dwUsernameLen = 0;
+ if (!GetUserNameA(NULL,&dwUsernameLen) &&
+ GetLastError()==ERROR_MORE_DATA && dwUsernameLen &&
+ (szUsername = HeapAlloc( GetProcessHeap(), 0, dwUsernameLen)))
+ {
+ szUsername[0]='\0';
+ GetUserNameA( szUsername, &dwUsernameLen );
+ }
+ SetLastError(dwError);
+
+ /* salt the hash with:
+ * - the user id
+ * - an "internal secret"
+ * - randomness (from the salt)
+ * - user-supplied entropy
+ */
+ if ((szUsername && !CryptHashData(hSaltHash,szUsername,dwUsernameLen,0)) ||
+ !CryptHashData(hSaltHash,CRYPT32_PROTECTDATA_SECRET,
+ strlen(CRYPT32_PROTECTDATA_SECRET),0) ||
+ !CryptHashData(hSaltHash,salt->pbData,salt->cbData,0) ||
+ (pOptionalEntropy && !CryptHashData(hSaltHash,
+ pOptionalEntropy->pbData,
+ pOptionalEntropy->cbData,0)))
+ {
+ ERR("CryptHashData\n");
+ rc = FALSE;
+ }
+
+ /* produce a symmetric key */
+ if (rc && !CryptDeriveKey(hProv,CRYPT32_PROTECTDATA_KEY_CALG,
+ hSaltHash,CRYPT_EXPORTABLE,phKey))
+ {
+ ERR("CryptDeriveKey\n");
+ rc = FALSE;
+ }
+
+ /* clean up */
+ CryptDestroyHash(hSaltHash);
+ if (szUsername) HeapFree( GetProcessHeap(), 0, szUsername );
+
+ return rc;
+}
+
+/* debugging tool to print the structures of a ProtectData call */
+static void
+crypt_report_func_input(DATA_BLOB* pDataIn,
+ DATA_BLOB* pOptionalEntropy,
+ CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct,
+ DWORD dwFlags)
+{
+ wine_dbg_printf("\tpPromptStruct: 0x%x\n",(unsigned int)pPromptStruct);
+ if (pPromptStruct)
+ {
+ wine_dbg_printf("\t\tcbSize: 0x%x\n",(unsigned int)pPromptStruct->cbSize);
+ wine_dbg_printf("\t\tdwPromptFlags: 0x%x\n",(unsigned int)pPromptStruct->dwPromptFlags);
+ wine_dbg_printf("\t\thwndApp: 0x%x\n",(unsigned int)pPromptStruct->hwndApp);
+ wine_dbg_printf("\t\tszPrompt: 0x%x %s\n",
+ (unsigned int)pPromptStruct->szPrompt,
+ pPromptStruct->szPrompt ? debugstr_w(pPromptStruct->szPrompt)
+ : "");
+ }
+ wine_dbg_printf("\tdwFlags: 0x%04x\n",(unsigned int)dwFlags);
+ wine_dbg_printf("\tpDataIn->cbData: %u\n",(unsigned int)pDataIn->cbData);
+ wine_dbg_printf("\tpDataIn->pbData: 0x%x\n",(unsigned int)pDataIn->pbData);
+ hexprint("pbData", pDataIn->pbData, pDataIn->cbData);
+ if (pOptionalEntropy)
+ {
+ wine_dbg_printf("\tpOptionalEntropy->cbData: %u\n",(unsigned int)pOptionalEntropy->cbData);
+ wine_dbg_printf("\tpOptionalEntropy->pbData: 0x%x\n",(unsigned int)pOptionalEntropy->pbData);
+ hexprint("pbData", pOptionalEntropy->pbData, pOptionalEntropy->cbData);
+ wine_dbg_printf("\t\t%s\n",debugstr_an(pOptionalEntropy->pbData,pOptionalEntropy->cbData));
+ }
+
+}
+
+static void
+announce_bad_opaque_data()
+{
+ wine_dbg_printf("CryptUnprotectData received the following pDataIn DATA_BLOB that seems to\n");
+ wine_dbg_printf("have NOT been generated by Wine:\n");
+}
+
More information about the wine-patches
mailing list