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@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
+
+@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@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);
+}
