[crypt32] implementation of CryptProtectData/CryptUnprotectData

Kees Cook kees at outflux.net
Tue May 17 00:02:23 CDT 2005


Hi, I'm back again.  :) This implements the crypt32 functions
CryptProtectData and CryptUnprotectData, using a best-guess at the true
Windows opaque data format and encryption methods.  It attempts to
follow the "details" in the MSDN article that described
CryptProtectData.

Please let me know how this all looks.  Hopefully I've got it mostly 
right this time.  If there is a more sane way to handle the structure
serialization/unserialization, I'm all ears.  

(Anyone paying really close attention will note that I uncovered some
crash bugs in rsaenh as a result of my crypt32 test cases.  Make sure
you've done a "cvs update" to catch those changes before running them.)

ChangeLog:
	Implements a best-guess of CryptProtectData/CryptUnprotectData.


-- 
Kees Cook                                            @outflux.net
-------------- next part --------------
Index: configure.ac
===================================================================
RCS file: /home/wine/wine/configure.ac,v
retrieving revision 1.352
diff -u -p -u -p -r1.352 configure.ac
--- configure.ac	16 May 2005 19:42:47 -0000	1.352
+++ configure.ac	17 May 2005 04:50:26 -0000
@@ -1536,6 +1536,7 @@ dlls/comctl32/tests/Makefile
 dlls/commdlg/Makefile
 dlls/crtdll/Makefile
 dlls/crypt32/Makefile
+dlls/crypt32/tests/Makefile
 dlls/cryptdll/Makefile
 dlls/ctl3d/Makefile
 dlls/d3d8/Makefile
Index: include/wincrypt.h
===================================================================
RCS file: /home/wine/wine/include/wincrypt.h,v
retrieving revision 1.31
diff -u -p -u -p -r1.31 wincrypt.h
--- include/wincrypt.h	24 Jan 2005 11:28:15 -0000	1.31
+++ include/wincrypt.h	17 May 2005 04:50:27 -0000
@@ -1089,6 +1089,15 @@ BOOL WINAPI CertCloseStore( HCERTSTORE h
 
 BOOL WINAPI CertFreeCertificateContext( PCCERT_CONTEXT pCertContext );
 
+/* crypt32.dll functions */
+BOOL WINAPI CryptProtectData( DATA_BLOB* pDataIn, LPCWSTR szDataDescr,
+ DATA_BLOB* pOptionalEntropy, PVOID pvReserved,
+ CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags, DATA_BLOB* pDataOut );
+
+BOOL WINAPI CryptUnprotectData( DATA_BLOB* pDataIn, LPWSTR* ppszDataDescr,
+ DATA_BLOB* pOptionalEntropy, PVOID pvReserved,
+ CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags, DATA_BLOB* pDataOut );
+
 #ifdef __cplusplus
 }
 #endif
Index: dlls/crypt32/Makefile.in
===================================================================
RCS file: /home/wine/wine/dlls/crypt32/Makefile.in,v
retrieving revision 1.9
diff -u -p -u -p -r1.9 Makefile.in
--- dlls/crypt32/Makefile.in	9 May 2005 14:42:35 -0000	1.9
+++ dlls/crypt32/Makefile.in	17 May 2005 04:50:28 -0000
@@ -9,6 +9,7 @@ IMPORTS   = advapi32 kernel32
 
 C_SRCS = \
 	cert.c \
+	protectdata.c \
 	main.c
 
 @MAKE_DLL_RULES@
Index: dlls/crypt32/crypt32.spec
===================================================================
RCS file: /home/wine/wine/dlls/crypt32/crypt32.spec,v
retrieving revision 1.19
diff -u -p -u -p -r1.19 crypt32.spec
--- dlls/crypt32/crypt32.spec	10 Nov 2004 01:31:50 -0000	1.19
+++ dlls/crypt32/crypt32.spec	17 May 2005 04:50:29 -0000
@@ -149,7 +149,7 @@
 @ stub CryptSignHashU
 @ stub CryptSignMessage
 @ stub CryptSignMessageWithKey
-@ stub CryptUnprotectData
+@ stdcall CryptUnprotectData(ptr ptr ptr ptr ptr long ptr)
 @ stub CryptUnregisterDefaultOIDFunction
 @ stub CryptUnregisterOIDFunction
 @ stub CryptUnregisterOIDInfo
Index: dlls/crypt32/main.c
===================================================================
RCS file: /home/wine/wine/dlls/crypt32/main.c,v
retrieving revision 1.19
diff -u -p -u -p -r1.19 main.c
--- dlls/crypt32/main.c	30 Nov 2004 21:39:00 -0000	1.19
+++ dlls/crypt32/main.c	17 May 2005 04:50:29 -0000
@@ -56,14 +56,6 @@ BOOL WINAPI I_CryptFreeLruCache(DWORD x)
     return FALSE;
 }
 
-BOOL WINAPI CryptProtectData(DATA_BLOB* pDataIn, LPCWSTR szDataDescr, DATA_BLOB* pOptionalEntropy,
-                             PVOID pvReserved, CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct,
-                             DWORD dwFlags, DATA_BLOB* pDataOut)
-{
-    FIXME("stub!\n");
-    return FALSE;
-}
-
 BOOL WINAPI CryptSIPRemoveProvider(GUID *pgProv)
 {
     FIXME("stub!\n");
--- /dev/null	2005-04-15 00:19:28.973360032 -0700
+++ dlls/crypt32/protectdata.c	2005-05-16 21:43:21.000000000 -0700
@@ -0,0 +1,1154 @@
+/*
+ * Copyright 2005 Kees Cook <kees at outflux.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+ * The Win32 CryptProtectData and CryptUnprotectData functions are meant
+ * to provide a mechanism for encrypting data on a machine where other users
+ * of the system can't be trusted.  It is used in many examples as a way
+ * to store username and password information to the registry, but store
+ * it not in the clear.
+ *
+ * The encryption is symmetric, but the method is unknown.  However, since
+ * it is keyed to the machine and the user, it is unlikely that the values
+ * would be portable.  Since programs must first call CryptProtectData to
+ * get a cipher text, the underlying system doesn't have to exactly
+ * match the real Windows version.  However, attempts have been made to
+ * at least try to look like the Windows version, including guesses at the
+ * purpose of various portions of the "opaque data blob" that is used.
+ *
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wincrypt.h"
+#include "winreg.h"
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(crypt);
+
+#define CRYPT32_PROTECTDATA_PROV      PROV_RSA_FULL
+#define CRYPT32_PROTECTDATA_HASH_CALG CALG_MD5
+#define CRYPT32_PROTECTDATA_KEY_CALG  CALG_RC2
+#define CRYPT32_PROTECTDATA_SALT_LEN  16
+
+#define CRYPT32_PROTECTDATA_SECRET "I'm hunting wabbits"
+
+/*
+ * The data format returned by the real Windows CryptProtectData seems
+ * to be something like this:
+
+ DWORD  count0;         - how many "info0_*[16]" blocks follow (was always 1)
+ BYTE   info0_0[16];    - unknown information
+ ...
+ DWORD  count1;         - how many "info1_*[16]" blocks follow (was always 1)
+ BYTE   info1_0[16];    - unknown information
+ ...
+ DWORD  null0;          - NULL "end of records"?
+ DWORD  str_len;        - length of WCHAR string including term
+ WCHAR  str[str_len];   - The "dataDescription" value
+ DWORD  unknown0;       - unknown value (seems large, but only WORD large)
+ DWORD  unknown1;       - unknown value (seems small, less than a BYTE)
+ DWORD  data_len;       - length of data (was 16 in samples)
+ BYTE   data[data_len]; - unknown data (fingerprint?)
+ DWORD  null1;          - NULL ?
+ DWORD  unknown2;       - unknown value (seems large, but only WORD large)
+ DWORD  unknown3;       - unknown value (seems small, less than a BYTE)
+ DWORD  salt_len;       - length of salt(?) data
+ BYTE   salt[salt_len]; - salt(?) for symmetric encryption
+ DWORD  cipher_len;     - length of cipher(?) data - was close to plain len
+ BYTE   cipher[cipher_len]; - cipher text?
+ DWORD  crc_len;        - length of fingerprint(?) data - was 20 byte==160b SHA1
+ BYTE   crc[crc_len];   - fingerprint of record?
+
+ * The data structures used in Wine are modelled after this guess.
+ */
+
+struct protect_data_t
+{
+    DWORD       count0;
+    DATA_BLOB   info0;        /* using this to hold crypt_magic_str */
+    DWORD       count1;
+    DATA_BLOB   info1;
+    DWORD       null0;
+    WCHAR *     szDataDescr;  /* serialized differently than the DATA_BLOBs */
+    DWORD       unknown0;     /* perhaps the HASH alg const should go here? */
+    DWORD       unknown1;
+    DATA_BLOB   data0;
+    DWORD       null1;
+    DWORD       unknown2;     /* perhaps the KEY alg const should go here? */
+    DWORD       unknown3;
+    DATA_BLOB   salt;
+    DATA_BLOB   cipher;
+    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.  Round up. */
+    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),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(WCHAR),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");
+}
+
+/***************************************************************************
+ * CryptProtectData     [CRYPT32.@]
+ *
+ * Generate Cipher data from given Plain and Entropy data.
+ *
+ * PARAMS
+ *  pDataIn          [I] Plain data to be enciphered
+ *  szDataDescr      [I] Optional Unicode string describing the Plain data
+ *  pOptionalEntropy [I] Optional entropy data to adjust cipher, can be NULL
+ *  pvReserved       [I] Reserved, must be NULL
+ *  pPromptStruct    [I] Structure describing if/how to prompt during ciphering
+ *  dwFlags          [I] Flags describing options to the ciphering
+ *  pDataOut         [O] Resulting Cipher data, for calls to CryptUnprotectData
+ *
+ * RETURNS
+ *  TRUE  If a Cipher was generated.
+ *  FALSE If something failed and no Cipher is available.
+ *
+ * FIXME
+ *  The true Windows encryption and keying mechanisms are unknown.
+ *
+ *  dwFlags and pPromptStruct are currently ignored.
+ *
+ * NOTES
+ *  Memory allocated in pDataOut must be freed with LocalFree.
+ *
+ */
+BOOL WINAPI CryptProtectData(DATA_BLOB* pDataIn,
+                             LPCWSTR szDataDescr,
+                             DATA_BLOB* pOptionalEntropy,
+                             PVOID pvReserved,
+                             CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct,
+                             DWORD dwFlags,
+                             DATA_BLOB* pDataOut)
+{
+    BOOL rc = FALSE;
+
+    HCRYPTPROV hProv;
+    struct protect_data_t protect_data;
+    HCRYPTHASH hHash;
+    HCRYPTKEY hKey;
+    DWORD dwLength;
+
+    TRACE("called\n");
+
+    SetLastError(ERROR_SUCCESS);
+
+    if (!pDataIn || !pDataOut)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        goto finished;
+    }
+
+    /* debug: show our arguments */
+    if (WINE_TRACE_ON(crypt))
+    {
+        crypt_report_func_input(pDataIn,pOptionalEntropy,pPromptStruct,dwFlags);
+        wine_dbg_printf("\tszDataDescr: 0x%x %s\n",(unsigned int)szDataDescr,
+                szDataDescr ? debugstr_w(szDataDescr) : "");
+    }
+
+    /* Windows appears to create an empty szDataDescr instead of maintaining
+     * a NULL */
+    if (!szDataDescr)
+        szDataDescr=(WCHAR[]){'\0'};
+
+    /* get crypt context */
+    if (!CryptAcquireContextW(&hProv,NULL,NULL,CRYPT32_PROTECTDATA_PROV,0))
+    {
+        ERR("CryptAcquireContextW failed\n");
+        goto finished;
+    }
+
+    /* populate our structure */
+    if (!fill_protect_data(&protect_data,szDataDescr,hProv))
+    {
+        ERR("fill_protect_data\n");
+        goto free_context;
+    }
+
+    /* load key */
+    if (!load_encryption_key(hProv,&protect_data.salt,pOptionalEntropy,&hKey))
+    {
+        goto free_protect_data;
+    }
+
+    /* create a hash for the encryption validation */
+    if (!CryptCreateHash(hProv,CRYPT32_PROTECTDATA_HASH_CALG,0,0,&hHash))
+    {
+        ERR("CryptCreateHash\n");
+        goto free_key;
+    }
+
+    /* calculate storage required */
+    dwLength=pDataIn->cbData;
+    if (CryptEncrypt(hKey, 0, TRUE, 0, pDataIn->pbData, &dwLength, 0) ||
+        GetLastError()!=ERROR_MORE_DATA)
+    {
+        ERR("CryptEncrypt\n");
+        goto free_hash;
+    }
+    TRACE("required encrypted storage: %u\n",(unsigned int)dwLength);
+
+    /* copy plain text into cipher area for CryptEncrypt call */
+    protect_data.cipher.cbData=dwLength;
+    if (!(protect_data.cipher.pbData=HeapAlloc( GetProcessHeap(), 0,
+                                                protect_data.cipher.cbData)))
+    {
+        ERR("HeapAlloc\n");
+        goto free_hash;
+    }
+    memcpy(protect_data.cipher.pbData,pDataIn->pbData,pDataIn->cbData);
+
+    /* encrypt! */
+    dwLength=pDataIn->cbData;
+    if (!CryptEncrypt(hKey, hHash, TRUE, 0, protect_data.cipher.pbData,
+                      &dwLength, protect_data.cipher.cbData))
+    {
+        ERR("CryptEncrypt %u\n",(unsigned int)GetLastError());
+        goto free_hash;
+    }
+    protect_data.cipher.cbData=dwLength;
+
+    /* debug: show the cipher */
+    if (WINE_TRACE_ON(crypt))
+    {
+        wine_dbg_printf("\tcipher.cbData: %u\n",
+                        (unsigned int)protect_data.cipher.cbData);
+        hexprint("pbData", protect_data.cipher.pbData,
+                           protect_data.cipher.cbData);
+    }
+
+    /* attach our fingerprint */
+    if (!convert_hash_to_blob(hHash, &protect_data.fingerprint))
+    {
+        ERR("convert_hash_to_blob\n");
+        goto free_hash;
+    }
+
+    /* serialize into an opaque blob */
+    if (!serialize(&protect_data, pDataOut))
+    {
+        ERR("serialize\n");
+        goto free_hash;
+    }
+
+    /* success! */
+    rc=TRUE;
+
+free_hash:
+    CryptDestroyHash(hHash);
+free_key:
+    CryptDestroyKey(hKey);
+free_protect_data:
+    free_protect_data(&protect_data);
+free_context:
+    CryptReleaseContext(hProv,0);
+finished:
+    /* If some error occured, and no error code was set, force one. */
+    if (!rc && GetLastError()==ERROR_SUCCESS)
+    {
+        SetLastError(ERROR_INVALID_DATA);
+    }
+
+    TRACE("returning %s\n", rc ? "ok" : "FAIL");
+
+    if (rc)
+    {
+        SetLastError(ERROR_SUCCESS);
+
+        if (WINE_TRACE_ON(crypt))
+        {
+            wine_dbg_printf("\tpDataOut->cbData: %u\n",(unsigned int)pDataOut->cbData);
+            wine_dbg_printf("\tpDataOut->pbData: 0x%x\n",(unsigned int)pDataOut->pbData);
+            hexprint("pbData", pDataOut->pbData, pDataOut->cbData);
+        }
+    }
+
+    return rc;
+}
+
+/***************************************************************************
+ * CryptUnprotectData   [CRYPT32.@]
+ *
+ * Generate Plain data and Description from given Cipher and Entropy data.
+ *
+ * PARAMS
+ *  pDataIn          [I] Cipher data to be decoded
+ *  ppszDataDescr    [O] Optional Unicode string describing the Plain data
+ *  pOptionalEntropy [I] Optional entropy data to adjust cipher, can be NULL
+ *  pvReserved       [I] Reserved, must be NULL
+ *  pPromptStruct    [I] Structure describing if/how to prompt during decoding
+ *  dwFlags          [I] Flags describing options to the decoding
+ *  pDataOut         [O] Resulting Plain data, from calls to CryptProtectData
+ *
+ * RETURNS
+ *  TRUE  If a Plain was generated.
+ *  FALSE If something failed and no Plain is available.
+ *
+ * FIXME
+ *  The true Windows encryption and keying mechanisms are unknown.
+ *
+ *  dwFlags and pPromptStruct are currently ignored.
+ *
+ * NOTES
+ *  Memory allocated in pDataOut and non-NULL ppszDataDescr must be freed
+ *  with LocalFree.
+ *
+ */
+BOOL WINAPI CryptUnprotectData(DATA_BLOB* pDataIn,
+                               LPWSTR * ppszDataDescr,
+                               DATA_BLOB* pOptionalEntropy,
+                               PVOID pvReserved,
+                               CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct,
+                               DWORD dwFlags,
+                               DATA_BLOB* pDataOut)
+{
+    BOOL rc = FALSE;
+
+    HCRYPTPROV hProv;
+    struct protect_data_t protect_data;
+    HCRYPTHASH hHash;
+    HCRYPTKEY hKey;
+    DWORD dwLength;
+
+    TRACE("called\n");
+
+    SetLastError(ERROR_SUCCESS);
+
+    if (!pDataIn || !pDataOut)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        goto finished;
+    }
+
+    /* debug: show our arguments */
+    if (WINE_TRACE_ON(crypt)) {
+        crypt_report_func_input(pDataIn,pOptionalEntropy,pPromptStruct,dwFlags);
+        wine_dbg_printf("\tppszDataDescr: 0x%x\n",(unsigned int)ppszDataDescr);
+    }
+
+    /* take apart the opaque blob */
+    if (!unserialize(pDataIn, &protect_data))
+    {
+        SetLastError(ERROR_INVALID_DATA);
+        announce_bad_opaque_data(pDataIn,pOptionalEntropy);
+        crypt_report_func_input(pDataIn,pOptionalEntropy,pPromptStruct,dwFlags);
+        goto finished;
+    }
+
+    /* perform basic validation on the resulting structure */
+    if (!valid_protect_data(&protect_data))
+    {
+        SetLastError(ERROR_INVALID_DATA);
+        announce_bad_opaque_data(pDataIn,pOptionalEntropy);
+        crypt_report_func_input(pDataIn,pOptionalEntropy,pPromptStruct,dwFlags);
+        goto free_protect_data;
+    }
+
+    /* get a crypt context */
+    if (!CryptAcquireContextW(&hProv,NULL,NULL,CRYPT32_PROTECTDATA_PROV,0))
+    {
+        ERR("CryptAcquireContextW failed\n");
+        goto free_protect_data;
+    }
+
+    /* load key */
+    if (!load_encryption_key(hProv,&protect_data.salt,pOptionalEntropy,&hKey))
+    {
+        goto free_context;
+    }
+
+    /* create a hash for the decryption validation */
+    if (!CryptCreateHash(hProv,CRYPT32_PROTECTDATA_HASH_CALG,0,0,&hHash))
+    {
+        ERR("CryptCreateHash\n");
+        goto free_key;
+    }
+
+    /* prepare for plaintext */
+    pDataOut->cbData=protect_data.cipher.cbData;
+    if (!(pDataOut->pbData=LocalAlloc( LPTR, pDataOut->cbData)))
+    {
+        ERR("HeapAlloc\n");
+        goto free_hash;
+    }
+    memcpy(pDataOut->pbData,protect_data.cipher.pbData,protect_data.cipher.cbData);
+
+    /* decrypt! */
+    if (!CryptDecrypt(hKey, hHash, TRUE, 0, pDataOut->pbData,
+                      &pDataOut->cbData) ||
+        /* check the hash fingerprint */
+        pDataOut->cbData > protect_data.cipher.cbData ||
+        !hash_matches_blob(hHash, &protect_data.fingerprint))
+    {
+        SetLastError(ERROR_INVALID_DATA);
+
+        LocalFree( pDataOut->pbData );
+        pDataOut->pbData = NULL;
+        pDataOut->cbData = 0;
+
+        goto free_hash;
+    }
+
+    /* Copy out the description */
+    dwLength = (lstrlenW(protect_data.szDataDescr)+1) * sizeof(WCHAR);
+    if (ppszDataDescr)
+    {
+        if (!(*ppszDataDescr = LocalAlloc(LPTR,dwLength)))
+        {
+            ERR("LocalAlloc (ppszDataDescr)\n");
+            goto free_hash;
+        }
+        else {
+            memcpy(*ppszDataDescr,protect_data.szDataDescr,dwLength);
+        }
+    }
+
+    /* success! */
+    rc = TRUE;
+
+free_hash:
+    CryptDestroyHash(hHash);
+free_key:
+    CryptDestroyKey(hKey);
+free_context:
+    CryptReleaseContext(hProv,0);
+free_protect_data:
+    free_protect_data(&protect_data);
+finished:
+    /* If some error occured, and no error code was set, force one. */
+    if (!rc && GetLastError()==ERROR_SUCCESS)
+    {
+        SetLastError(ERROR_INVALID_DATA);
+    }
+
+    TRACE("returning %s\n", rc ? "ok" : "FAIL");
+
+    if (rc) {
+        SetLastError(ERROR_SUCCESS);
+
+        if (WINE_TRACE_ON(crypt))
+        {
+            if (ppszDataDescr)
+            {
+                wine_dbg_printf("\tszDataDescr: %s\n",debugstr_w(*ppszDataDescr));
+            }
+            wine_dbg_printf("\tpDataOut->cbData: %u\n",(unsigned int)pDataOut->cbData);
+            wine_dbg_printf("\tpDataOut->pbData: 0x%x\n",(unsigned int)pDataOut->pbData);
+            hexprint("pbData", pDataOut->pbData, pDataOut->cbData);
+        }
+    }
+
+    return rc;
+}
+
+/* vi:set ai ts=4 sw=4 expandtab: */
--- /dev/null	2005-04-15 00:19:28.973360032 -0700
+++ dlls/crypt32/tests/Makefile.in	2005-04-05 17:58:16.000000000 -0700
@@ -0,0 +1,14 @@
+EXTRADEFS = -D_CRYPT32_
+TOPSRCDIR = @top_srcdir@
+TOPOBJDIR = ../../..
+SRCDIR    = @srcdir@
+VPATH     = @srcdir@
+TESTDLL   = crypt32.dll
+IMPORTS   = crypt32
+
+CTESTS = \
+	protectdata.c
+
+ at MAKE_TEST_RULES@
+
+### Dependencies:
--- /dev/null	2005-04-15 00:19:28.973360032 -0700
+++ dlls/crypt32/tests/protectdata.c	2005-05-15 22:27:05.000000000 -0700
@@ -0,0 +1,235 @@
+/*
+ * Unit test suite for crypt32.dll's CryptProtectData/CryptUnprotectData
+ *
+ * Copyright 2005 Kees Cook <kees at outflux.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <windef.h>
+#include <winbase.h>
+
+#ifndef STANDALONE
+#include "wine/test.h"
+#define ok2 ok
+#else
+/* To build outside Wine tree, compile with cl -DSTANDALONE -D_X86_ protectdata.c crypt32.lib */
+#include <assert.h>
+#include <stdio.h>
+#define START_TEST(name) main(int argc, char **argv)
+#define ok(condition, msg)       \
+    do { if(!(condition)) {  \
+        fprintf(stderr,"failed at %d, msg:" msg "\n",__LINE__); \
+        exit(1);         \
+    } } while(0)
+#define ok2(condition, msg, arg) \
+    do { if(!(condition)) {  \
+        fprintf(stderr,"failed at %d, msg:" msg "\n",__LINE__, arg); \
+        exit(1);         \
+    } } while(0)
+#define todo_wine
+#endif
+
+#include <winerror.h>
+#include <wincrypt.h>
+
+static const char * secret  = "I am a super secret string that no one can see!";
+static const char * secret2 = "I am a super secret string indescribable string";
+static const char * key = "Wibble wibble wibble";
+static const WCHAR desc[] = {'U','l','t','r','a',' ','s','e','c','r','e','t',' ','t','e','s','t',' ','m','e','s','s','a','g','e',0};
+static BOOL protected = FALSE; /* if true, the unprotect tests can run */
+static DATA_BLOB cipher;
+static DATA_BLOB cipher_entropy;
+static DATA_BLOB cipher_no_desc;
+
+static void test_cryptprotectdata(void)
+{
+    LONG r;
+    DATA_BLOB plain;
+    DATA_BLOB entropy;
+
+    plain.pbData=(void*)secret;
+    plain.cbData=strlen(secret)+1;
+
+    entropy.pbData=(void*)key;
+    entropy.cbData=strlen(key)+1;
+
+    SetLastError(0xDEADBEEF);
+    protected = CryptProtectData(NULL,desc,NULL,NULL,NULL,0,&cipher);
+    ok(!protected, "Encrypting without plain data source.\n");
+    r = GetLastError();
+    ok2(r == ERROR_INVALID_PARAMETER, "Wrong (%lu) GetLastError seen\n",r);
+
+    SetLastError(0xDEADBEEF);
+    protected = CryptProtectData(&plain,desc,NULL,NULL,NULL,0,NULL);
+    ok(!protected, "Encrypting without cipher destination.\n");
+    r = GetLastError();
+    ok2(r == ERROR_INVALID_PARAMETER, "Wrong (%lu) GetLastError seen\n",r);
+
+    cipher.pbData=NULL;
+    cipher.cbData=0;
+
+    /* without entropy */
+    SetLastError(0xDEADBEEF);
+    protected = CryptProtectData(&plain,desc,NULL,NULL,NULL,0,&cipher);
+    ok(protected, "Encrypting without entropy.\n");
+    r = GetLastError();
+    ok2(r == ERROR_SUCCESS, "Wrong (%lu) GetLastError seen\n",r);
+
+    cipher_entropy.pbData=NULL;
+    cipher_entropy.cbData=0;
+
+    /* with entropy */
+    SetLastError(0xDEADBEEF);
+    protected = CryptProtectData(&plain,desc,&entropy,NULL,NULL,0,&cipher_entropy);
+    ok(protected, "Encrypting with entropy.\n");
+    r = GetLastError();
+    ok2(r == ERROR_SUCCESS, "Wrong (%lu) GetLastError seen\n",r);
+
+    cipher_no_desc.pbData=NULL;
+    cipher_no_desc.cbData=0;
+
+    /* with entropy but no description */
+    plain.pbData=(void*)secret2;
+    plain.cbData=strlen(secret2)+1;
+    SetLastError(0xDEADBEEF);
+    protected = CryptProtectData(&plain,NULL,&entropy,NULL,NULL,0,&cipher_no_desc);
+    ok(protected, "Encrypting with entropy and no description.\n");
+    r = GetLastError();
+    ok2(r == ERROR_SUCCESS, "Wrong (%lu) GetLastError seen\n",r);
+}
+
+static void test_cryptunprotectdata(void)
+{
+    LONG r;
+    DATA_BLOB plain;
+    DATA_BLOB entropy;
+    BOOL okay;
+    WCHAR * data_desc;
+
+    entropy.pbData=(void*)key;
+    entropy.cbData=strlen(key)+1;
+
+    ok(protected, "CryptProtectData failed to run, so I can't test its output\n");
+    if (!protected) return;
+
+    plain.pbData=NULL;
+    plain.cbData=0;
+
+    SetLastError(0xDEADBEEF);
+    okay = CryptUnprotectData(&cipher,NULL,NULL,NULL,NULL,0,NULL);
+    ok(!okay,"Decrypting without destination\n");
+    r = GetLastError();
+    ok2(r == ERROR_INVALID_PARAMETER, "Wrong (%lu) GetLastError seen\n",r);
+
+    SetLastError(0xDEADBEEF);
+    okay = CryptUnprotectData(NULL,NULL,NULL,NULL,NULL,0,&plain);
+    ok(!okay,"Decrypting without source\n");
+    r = GetLastError();
+    ok2(r == ERROR_INVALID_PARAMETER, "Wrong (%lu) GetLastError seen\n",r);
+
+    plain.pbData=NULL;
+    plain.cbData=0;
+
+    SetLastError(0xDEADBEEF);
+    okay = CryptUnprotectData(&cipher_entropy,NULL,NULL,NULL,NULL,0,&plain);
+    ok(!okay,"Decrypting without needed entropy\n");
+    r = GetLastError();
+    ok2(r == ERROR_INVALID_DATA, "Wrong (%lu) GetLastError seen\n", r);
+
+    plain.pbData=NULL;
+    plain.cbData=0;
+    data_desc=NULL;
+
+    /* without entropy */
+    SetLastError(0xDEADBEEF);
+    okay = CryptUnprotectData(&cipher,&data_desc,NULL,NULL,NULL,0,&plain);
+    ok(okay,"Decrypting without entropy\n");
+    r = GetLastError();
+    ok2(r == ERROR_SUCCESS, "Wrong (%lu) GetLastError seen\n",r);
+
+    ok(plain.pbData!=NULL,"Plain DATA_BLOB missing data\n");
+    ok(plain.cbData==strlen(secret)+1,"Plain DATA_BLOB wrong length\n");
+    ok(!strcmp(plain.pbData,secret),"Plain does not match secret\n");
+    ok(data_desc!=NULL,"Description not allocated\n");
+    ok(!lstrcmpW(data_desc,desc),"Description does not match\n");
+
+    LocalFree(plain.pbData);
+    LocalFree(data_desc);
+
+    plain.pbData=NULL;
+    plain.cbData=0;
+    data_desc=NULL;
+
+    /* with wrong entropy */
+    SetLastError(0xDEADBEEF);
+    okay = CryptUnprotectData(&cipher_entropy,&data_desc,&cipher_entropy,NULL,NULL,0,&plain);
+    ok(!okay,"Decrypting with wrong entropy\n");
+    r = GetLastError();
+    ok2(r == ERROR_INVALID_DATA, "Wrong (%lu) GetLastError seen\n",r);
+
+    /* with entropy */
+    SetLastError(0xDEADBEEF);
+    okay = CryptUnprotectData(&cipher_entropy,&data_desc,&entropy,NULL,NULL,0,&plain);
+    ok(okay,"Decrypting with entropy\n");
+    r = GetLastError();
+    ok2(r == ERROR_SUCCESS, "Wrong (%lu) GetLastError seen\n",r);
+
+    ok(plain.pbData!=NULL,"Plain DATA_BLOB missing data\n");
+    ok(plain.cbData==strlen(secret)+1,"Plain DATA_BLOB wrong length\n");
+    ok(!strcmp(plain.pbData,secret),"Plain does not match secret\n");
+    ok(data_desc!=NULL,"Description not allocated\n");
+    ok(!lstrcmpW(data_desc,desc),"Description does not match\n");
+
+    LocalFree(plain.pbData);
+    LocalFree(data_desc);
+
+    plain.pbData=NULL;
+    plain.cbData=0;
+    data_desc=NULL;
+
+    /* with entropy but no description */
+    SetLastError(0xDEADBEEF);
+    okay = CryptUnprotectData(&cipher_no_desc,&data_desc,&entropy,NULL,NULL,0,&plain);
+    ok(okay,"Decrypting with entropy and no description\n");
+    r = GetLastError();
+    ok2(r == ERROR_SUCCESS, "Wrong (%lu) GetLastError seen\n",r);
+
+    ok(plain.pbData!=NULL,"Plain DATA_BLOB missing data\n");
+    ok(plain.cbData==strlen(secret2)+1,"Plain DATA_BLOB wrong length\n");
+    ok(!strcmp(plain.pbData,secret2),"Plain does not match secret\n");
+    ok(data_desc!=NULL,"Description not allocated\n");
+    ok(data_desc[0]=='\0',"Description not empty\n");
+
+    LocalFree(plain.pbData);
+
+    plain.pbData=NULL;
+    plain.cbData=0;
+}
+
+START_TEST(protectdata)
+{
+    protected=FALSE;
+
+    test_cryptprotectdata();
+    test_cryptunprotectdata();
+
+    /* deinit globals here */
+    if (cipher.pbData) LocalFree(cipher.pbData);
+    if (cipher_entropy.pbData) LocalFree(cipher_entropy.pbData);
+    if (cipher_no_desc.pbData) LocalFree(cipher_no_desc.pbData);
+}


More information about the wine-patches mailing list