crypt32: CryptProtectData/CryptUnprotectData take 4

Kees Cook kees at outflux.net
Wed Apr 6 10:35:12 CDT 2005


ChangeLog:
    Black-box implementation of CryptProtectData/CryptUnprotectData

Since "take 3", this includes several more suggestions/corrections.  The 
tests were rechecked on WinXP and Wine.

-- 
Kees Cook                                            @outflux.net
-------------- next part --------------
Index: configure.ac
===================================================================
RCS file: /home/wine/wine/configure.ac,v
retrieving revision 1.345
diff -u -p -u -p -r1.345 configure.ac
--- configure.ac	28 Mar 2005 09:58:45 -0000	1.345
+++ configure.ac	6 Apr 2005 15:25:29 -0000
@@ -1529,6 +1529,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	6 Apr 2005 15:25:31 -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.8
diff -u -p -u -p -r1.8 Makefile.in
--- dlls/crypt32/Makefile.in	1 Mar 2004 21:19:37 -0000	1.8
+++ dlls/crypt32/Makefile.in	6 Apr 2005 15:25:32 -0000
@@ -8,6 +8,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	6 Apr 2005 15:25:32 -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	6 Apr 2005 15:25:32 -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-02-11 19:52:11.491673144 -0800
+++ dlls/crypt32/protectdata.c	2005-04-06 08:23:25.460507896 -0700
@@ -0,0 +1,747 @@
+/*
+ * 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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wincrypt.h"
+#include "winreg.h"
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(crypt);
+
+#define CRYPT_MAX_REGISTRY_NAME_LENGTH 255
+static const WCHAR wszProtectDataMap[] = {
+    'S','o','f','t','w','a','r','e','\\',
+    'W','i','n','e','\\',
+    'C','r','y','p','t','\\',
+    'P','r','o','t','e','c','t','D','a','t','a','\\',
+    'M','a','p',
+    0};
+static const WCHAR wszCipher[] = {'C','i','p','h','e','r',0};
+static const WCHAR wszPlain[] = {'P','l','a','i','n',0};
+static const WCHAR wszEntropy[] = {'E','n','t','r','o','p','y',0};
+static const WCHAR wszDataDescr[] = {'D','a','t','a','D','e','s','c','r',0};
+
+/*
+ * 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 just has to track the
+ * plain/entropy/cipher triplet to be able to return the plain text on a
+ * later call to CryptUnprotectData.
+ *
+ * These functions DO NOT encrypt the data, but rather, track the calls
+ * in the registry, so that programs that rely on this mechanism will
+ * still function.  Some programs refuse to run unless they can successfully
+ * call CryptProtectData and CryptUnprotectData, getting the expected values
+ * back.
+ *
+ * To store the triplets, these functions use the registry:
+ *
+ * Registry Layout:
+ *     HKEY_CURRENT_USER\Software\Wine\Crypt\ProtectData\Map\[index]
+ *         Cipher:          HEX string
+ *         Entropy:         HEX string
+ *         DataDescription: WCHAR
+ *         Plain:           HEX string
+ *
+ * If a call is made to CryptUnprotectData for an unknown cipher/entropy
+ * pair, a new registry entry is created, but without a plain text.
+ *
+ */
+
+
+/*
+ * Complain to the user about a missing plain text they can add to the
+ * registry
+ */
+#define CRYPT_MISSING_PLAIN do { \
+           FIXME("Plain text for CryptUnprotectData not know -- please add one manually to %s\n", debugstr_w(wszProtectDataMap) ); \
+    } while(0)
+
+/*
+ * Creates a registry value, falling back to a string default.
+ * If fallback happens, the stored value is written to pDataOut
+ * if it isn't NULL
+ */
+static BOOL
+crypt_write_registry_value(HKEY hkeyOpen,
+                           const WCHAR * wszName,
+                           DWORD dwType,
+                           DATA_BLOB *pDataIn,
+                           WCHAR * wszDefault,
+                           DATA_BLOB *pDataOut)
+{
+    LONG r;
+    DATA_BLOB pData;
+
+    if (pDataIn)
+    {
+        pData.pbData=pDataIn->pbData;
+        pData.cbData=pDataIn->cbData;
+    }
+    else
+    {
+        pData.pbData=(void*)wszDefault;
+        pData.cbData=(lstrlenW(wszDefault)+1)*sizeof(WCHAR);
+
+        if (pDataOut)
+        {
+            pDataOut->cbData=pData.cbData;
+            pDataOut->pbData=LocalAlloc(LPTR,pDataOut->cbData);
+            memcpy(pDataOut->pbData,pData.pbData,pData.cbData);
+        }
+    }
+
+    r = RegSetValueExW(hkeyOpen,wszName,0,dwType, pData.pbData,pData.cbData);
+    return (r == ERROR_SUCCESS);
+}
+
+/*
+ * Creates an entire ProtectData registry Map entry
+ */
+static BOOL
+crypt_create_registry_item(DATA_BLOB *pDataCipher,
+                           DATA_BLOB *pDataPlain,
+                           DATA_BLOB *pOptionalEntropy,
+                           WCHAR * szDataDescr,
+                           DATA_BLOB *pDataOut)
+{
+    LONG r;
+    BOOL okay=TRUE;
+    BOOL bFound=FALSE;
+    HKEY hkeyMap;
+    DWORD dwIndex;
+    HKEY hkeyOpen;
+    WCHAR wszIndexKey[CRYPT_MAX_REGISTRY_NAME_LENGTH];
+
+    r = RegOpenKeyExW(HKEY_CURRENT_USER, wszProtectDataMap, 0, KEY_ALL_ACCESS, &hkeyMap);
+    if (r != ERROR_SUCCESS)
+    {
+        ERR("ProtectData registry not found\n");
+        return FALSE;
+    }        
+
+    /* set our search limit to 1024, arbitrarily */
+    for (dwIndex = 0; !bFound && dwIndex<1024; dwIndex++)
+    {
+        DWORD dwDisposition;
+        int i, len;
+        char buf[CRYPT_MAX_REGISTRY_NAME_LENGTH];
+        sprintf(buf,"%u",(unsigned int)dwIndex);
+        len=strlen(buf)+1;
+        for (i=0; i<len; i++) {
+            wszIndexKey[i]=buf[i];
+        }
+
+        r = RegCreateKeyExW(hkeyMap, wszIndexKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkeyOpen, &dwDisposition);
+        if (r != ERROR_SUCCESS)
+           continue;
+        if (dwDisposition == REG_OPENED_EXISTING_KEY)
+        {
+            /* already exists, skip */
+            RegCloseKey(hkeyOpen);
+            continue;
+        }
+
+        bFound=TRUE;
+    }
+    RegCloseKey(hkeyMap);
+
+    if (!bFound)
+    {
+        ERR("Cannot find room for new ProtectData registry item\n");
+        return FALSE;
+    }
+
+    if (!crypt_write_registry_value(hkeyOpen, wszCipher, REG_BINARY,
+                               pDataCipher, wszIndexKey, pDataOut))
+        okay=FALSE;
+    if (pDataPlain)
+    {
+        if (!crypt_write_registry_value(hkeyOpen, wszPlain, REG_BINARY,
+                                   pDataPlain, NULL, pDataOut))
+            okay=FALSE;
+    } 
+    else
+    {
+        CRYPT_MISSING_PLAIN;
+    }
+    if (szDataDescr)
+    {
+        if (!crypt_write_registry_value(hkeyOpen, wszDataDescr, REG_SZ,
+                                   NULL, szDataDescr, NULL))
+            okay=FALSE;
+    }
+    if (pOptionalEntropy)
+    {
+        if (!crypt_write_registry_value(hkeyOpen, wszEntropy, REG_BINARY,
+                                   pOptionalEntropy, NULL, NULL))
+            okay=FALSE;
+    }
+
+    RegCloseKey(hkeyOpen);
+    return okay;
+}
+
+
+/* 
+ * Top-level searching function, which uses a callback to evaluate
+ * if a given registry item matches.  If the callback returns TRUE,
+ * this function stores the dwMaxValueLen & hkey, and returns TRUE.
+ * It is up to the caller to close the open registry item hkey.
+ */
+static BOOL
+crypt_search_registry(HKEY *phkeyOut,
+                      DWORD *pMaxValueLen,
+                      BOOL (*reg_match_callback)(HKEY hkey,
+                                                  DWORD maxValueLen,
+                                                 DATA_BLOB *pDataMatch,
+                                                 DATA_BLOB *pOptionalEntropy),
+                      DATA_BLOB *pDataToMatch,
+                      DATA_BLOB *pOptionalEntropy)
+{
+    LONG r;
+    HKEY hkeyMap = NULL;
+    BOOL bFound = FALSE;
+    DWORD dwIndex;
+
+    TRACE("called\n");
+
+    if (!pDataToMatch || !phkeyOut || !reg_match_callback)
+        return FALSE;
+
+    r = RegOpenKeyExW(HKEY_CURRENT_USER, wszProtectDataMap, 0, KEY_READ, &hkeyMap);
+    if (r != ERROR_SUCCESS)
+    {
+        ERR("ProtectData registry not found\n");
+        return FALSE;
+    }        
+
+    for (dwIndex = 0; !bFound; dwIndex++)
+    {
+        HKEY hkeyIndex;
+        WCHAR wszIndexKey[CRYPT_MAX_REGISTRY_NAME_LENGTH];
+        DWORD dwIndexKeyLength=CRYPT_MAX_REGISTRY_NAME_LENGTH;
+        DWORD dwMaxValueLen;
+
+        r = RegEnumKeyExW(hkeyMap, dwIndex, wszIndexKey, &dwIndexKeyLength, NULL, NULL, NULL, NULL);
+        if (r != ERROR_SUCCESS)
+            break;
+        r = RegOpenKeyExW(hkeyMap, wszIndexKey, 0, KEY_READ, &hkeyIndex);
+        if (r != ERROR_SUCCESS)
+            break;
+
+        /* don't want the function-name prefixing from "TRACE" */
+        if (WINE_TRACE_ON(crypt))
+            wine_dbg_printf("\tChecking Map Index %s\n", debugstr_w(wszIndexKey));
+
+        r = RegQueryInfoKeyW(hkeyIndex, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwMaxValueLen, NULL, NULL);
+        if (r != ERROR_SUCCESS)
+        {
+            RegCloseKey(hkeyIndex);
+            break;
+        }
+
+        if (reg_match_callback(hkeyIndex,dwMaxValueLen,
+                               pDataToMatch,pOptionalEntropy))
+        {
+            bFound=TRUE;
+            if (phkeyOut) *phkeyOut=hkeyIndex;
+            if (pMaxValueLen) *pMaxValueLen=dwMaxValueLen;
+        }
+        else
+        {
+            RegCloseKey(hkeyIndex);
+        }
+    }
+
+    if (!bFound)
+    {
+        TRACE("no matches\n");
+    }
+
+    RegCloseKey(hkeyMap);
+    return bFound;
+}
+
+
+/*
+ * Utility function for matching Entropy against a given registry item.
+ */
+static BOOL
+crypt_reg_match_entropy(HKEY hkeyIndex,
+                    DWORD maxValueLen,
+                    DATA_BLOB *pOptionalEntropy)
+{
+    LONG r;
+    BYTE * pbValue;
+    DWORD dwValueLen;
+
+    dwValueLen = maxValueLen;
+    pbValue = HeapAlloc(GetProcessHeap(), 0, maxValueLen);
+
+    r = RegQueryValueExW(hkeyIndex, wszEntropy, NULL, NULL, pbValue, &dwValueLen);
+    if (r != ERROR_SUCCESS)
+    {
+        HeapFree(GetProcessHeap(), 0, pbValue);
+
+        /* if neither have entropy, it's a match */
+        if (!pOptionalEntropy)
+            return TRUE;
+
+        return FALSE;
+    }
+
+    if (!pOptionalEntropy)
+    {
+        /* if there was stored entropy, but none passed in, it's not a match */
+        HeapFree(GetProcessHeap(), 0, pbValue);
+        return FALSE;
+    }
+
+    /* Does the Entropy match? */
+    if (dwValueLen!=pOptionalEntropy->cbData ||
+        memcmp(pOptionalEntropy->pbData,pbValue,dwValueLen))
+    {
+        HeapFree(GetProcessHeap(), 0, pbValue);
+        return FALSE;
+    }
+
+    HeapFree(GetProcessHeap(), 0, pbValue);
+
+    /* don't want the function-name prefix that "TRACE" writes */
+    if (WINE_TRACE_ON(crypt))
+        wine_dbg_printf("\tEntropy matched\n");
+
+    return TRUE;
+}
+
+/*
+ * registry matching callback for plain-text searches.  This is used
+ * to find already "encrypted" items.
+ */
+static BOOL crypt_reg_callback_match_plain(HKEY hkeyIndex,
+                                           DWORD maxValueLen,
+                                            DATA_BLOB *pDataMatch,
+                                            DATA_BLOB *pOptionalEntropy)
+{
+    LONG r;
+    BYTE * pbValue;
+    DWORD dwValueLen;
+
+    if (!pDataMatch)
+        return FALSE;
+
+    pbValue = HeapAlloc(GetProcessHeap(), 0, maxValueLen);
+
+    dwValueLen = maxValueLen;
+    r = RegQueryValueExW(hkeyIndex, wszPlain, NULL, NULL, pbValue, &dwValueLen);
+    if (r != ERROR_SUCCESS)
+    {
+        HeapFree(GetProcessHeap(), 0, pbValue);
+        return FALSE;
+    }
+
+    /* Does the Plain match? */
+    if (dwValueLen!=pDataMatch->cbData ||
+        memcmp(pDataMatch->pbData,pbValue,dwValueLen))
+    {
+        HeapFree(GetProcessHeap(), 0, pbValue);
+        return FALSE;
+    }
+    /* don't want the function-name prefix that "TRACE" writes */
+    if (WINE_TRACE_ON(crypt))
+        wine_dbg_printf("\tPlain matched\n");
+
+    HeapFree(GetProcessHeap(), 0, pbValue);
+
+    if (!crypt_reg_match_entropy(hkeyIndex,maxValueLen,pOptionalEntropy))
+        return FALSE;
+
+    return TRUE;
+}
+
+/*
+ * registry matching callback for cipher-text searches.  This is used
+ * to find "encrypted" items based on their cipher text.
+ * One thing different here is that cipher may sometimes be NULL padded
+ * if they are unexpectedly small (<256 bytes).
+ */
+static BOOL crypt_reg_callback_match_cipher(HKEY hkeyIndex,
+                                            DWORD maxValueLen,
+                                            DATA_BLOB *pDataMatch,
+                                            DATA_BLOB *pOptionalEntropy)
+{
+    LONG r;
+    BYTE * pbValue;
+    DWORD dwValueLen;
+
+    if (!pDataMatch)
+        return FALSE;
+
+    /* Need to NULL-pad ciphers */
+    if (maxValueLen<pDataMatch->cbData)
+        maxValueLen=pDataMatch->cbData;
+    pbValue = HeapAlloc(GetProcessHeap(), 0, maxValueLen);
+
+    dwValueLen = maxValueLen;
+    r = RegQueryValueExW(hkeyIndex, wszCipher, NULL, NULL, pbValue, &dwValueLen);
+    if (r != ERROR_SUCCESS)
+    {
+        HeapFree(GetProcessHeap(), 0, pbValue);
+        return FALSE;
+    }
+    /* Perform NULL padding */
+    for (; dwValueLen<pDataMatch->cbData; dwValueLen++)
+        pbValue[dwValueLen]=0;
+
+    /* Does the Cipher match? */
+    if (dwValueLen!=pDataMatch->cbData ||
+        memcmp(pDataMatch->pbData,pbValue,dwValueLen))
+    {
+        HeapFree(GetProcessHeap(), 0, pbValue);
+        return FALSE;
+    }
+
+    /* don't want the function-name prefix that "TRACE" writes */
+    if (WINE_TRACE_ON(crypt))
+        wine_dbg_printf("\tCipher matched\n");
+
+    HeapFree(GetProcessHeap(), 0, pbValue);
+
+    if (!crypt_reg_match_entropy(hkeyIndex,maxValueLen,pOptionalEntropy))
+        return FALSE;
+
+    return TRUE;
+}
+
+static BOOL
+crypt_find_cipher_by_plain(DATA_BLOB* pDataOut, DATA_BLOB* pDataIn,
+                   DATA_BLOB* pOptionalEntropy, LPCWSTR szDataDescr)
+{
+    LONG r;
+    HKEY hkeyIndex;
+    DWORD maxValueLen;
+    DWORD dwValueLen;
+    BYTE * pbValue;
+    BOOL result=FALSE;
+
+    TRACE("called\n");
+
+    if (!crypt_search_registry(&hkeyIndex,&maxValueLen,
+                               crypt_reg_callback_match_plain,
+                               pDataIn,pOptionalEntropy))
+    {
+        return result;
+    }
+    /* found the registry entry */
+
+       pbValue = HeapAlloc(GetProcessHeap(), 0, maxValueLen);
+
+    dwValueLen = maxValueLen;
+    r = RegQueryValueExW(hkeyIndex, wszCipher, NULL, NULL, pbValue, &dwValueLen);
+    if (r == ERROR_SUCCESS)
+    {
+        /* make a copy of the cipher text */
+        pDataOut->cbData=dwValueLen;
+        pDataOut->pbData=LocalAlloc(LPTR,pDataOut->cbData);
+        memcpy(pDataOut->pbData,pbValue,pDataOut->cbData);
+
+        result=TRUE;
+    }
+    else
+    {
+        ERR("\tCipher value not found\n");
+    }
+
+    HeapFree(GetProcessHeap(), 0, pbValue);
+    RegCloseKey(hkeyIndex);
+    return result;
+}
+
+static BOOL
+crypt_find_plain_by_cipher(DATA_BLOB* pDataOut, LPWSTR *ppszDataDescr,
+                   DATA_BLOB* pDataIn, DATA_BLOB* pOptionalEntropy)
+{
+    LONG r;
+    HKEY hkeyIndex;
+    DWORD maxValueLen;
+    DWORD dwValueLen;
+    BYTE * pbValue;
+    BOOL result=FALSE;
+
+    TRACE("called\n");
+
+    if (!crypt_search_registry(&hkeyIndex,&maxValueLen,
+                               crypt_reg_callback_match_cipher,
+                               pDataIn,pOptionalEntropy))
+    {
+        /* no matching cipher/entropy, add a placeholder */
+        crypt_create_registry_item(pDataIn,NULL,pOptionalEntropy,NULL,NULL);
+        return result;
+    }
+    /* found the registry entry */
+
+    pbValue = HeapAlloc(GetProcessHeap(), 0, maxValueLen);
+
+    dwValueLen = maxValueLen;
+    r = RegQueryValueExW(hkeyIndex, wszPlain, NULL, NULL, pbValue, &dwValueLen);
+    if (r == ERROR_SUCCESS)
+    {
+        /* copy the plain text */
+        pDataOut->cbData=dwValueLen;
+        pDataOut->pbData=LocalAlloc(LPTR,pDataOut->cbData);
+        memcpy(pDataOut->pbData,pbValue,pDataOut->cbData);
+
+        /* copy the DataDescription */
+        if (ppszDataDescr)
+        {
+            dwValueLen = maxValueLen;
+            r = RegQueryValueExW(hkeyIndex, wszDataDescr, NULL, NULL, pbValue, &dwValueLen);
+            if (r != ERROR_SUCCESS)
+            {
+                /* if we don't have one stored, return an empty string */
+                *ppszDataDescr=LocalAlloc(LPTR,sizeof(WCHAR)*2);
+                (*ppszDataDescr)[0]='\0';
+            }
+            else {
+                *ppszDataDescr=LocalAlloc(LPTR,dwValueLen);
+                memcpy(*ppszDataDescr,pbValue,dwValueLen);
+            }
+        }
+
+        result = TRUE;
+    }
+    else
+    {
+        /* remind about placeholder entry */
+        CRYPT_MISSING_PLAIN;
+    }
+
+    HeapFree(GetProcessHeap(), 0, pbValue);
+    RegCloseKey(hkeyIndex);
+    return result;
+}
+
+/* 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;
+}
+
+/* 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);
+    hexprint("pbData", pDataIn->pbData, pDataIn->cbData);
+    if (pOptionalEntropy)
+    {
+        wine_dbg_printf("\tpOptionalEntropy->cbData: %u\n",(unsigned int)pOptionalEntropy->cbData);
+        hexprint("pbData", pOptionalEntropy->pbData, pOptionalEntropy->cbData);
+        wine_dbg_printf("\t\t%s\n",debugstr_an(pOptionalEntropy->pbData,pOptionalEntropy->cbData));
+    }
+
+}
+
+/***************************************************************************
+ * 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
+ *  As it exists, this function does NOT encrypt data, it just records the
+ *  plain/entropy/description in the Registry for later retrieval on calls
+ *  to CryptUnprotectData since 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.
+ *
+ * BUGS
+ *  If a prior plain/entropy pair is saved with CryptProtectData, the
+ *  DataDescr used (existing or not) will always stay.  Even if you call
+ *  CryptProtectData again with the same plain/entropy, the new DataDescr
+ *  will be ignored.  The registry entry must be directly edited.
+ *
+ */
+BOOL WINAPI CryptProtectData(DATA_BLOB* pDataIn,
+                             LPCWSTR szDataDescr,
+                             DATA_BLOB* pOptionalEntropy,
+                             PVOID pvReserved,
+                             CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct,
+                             DWORD dwFlags,
+                             DATA_BLOB* pDataOut)
+{
+    TRACE("called\n");
+
+    SetLastError(ERROR_SUCCESS);
+
+    if (!pDataIn || !pDataOut)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return FALSE;
+    }
+
+    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) : "");
+    }
+
+    if (crypt_find_cipher_by_plain(pDataOut, pDataIn, pOptionalEntropy, szDataDescr))
+    {
+        return TRUE;
+    }
+    if (crypt_create_registry_item(NULL,pDataIn,pOptionalEntropy,(WCHAR*)szDataDescr,pDataOut))
+    {
+        return TRUE;
+    }
+
+    SetLastError(ERROR_INVALID_PARAMETER);
+    return FALSE;
+}
+/***************************************************************************
+ * 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
+ *  As it exists, this function does NOT decrypt data, it just looks up the
+ *  plain/description in the Registry stored during earlier calls
+ *  to CryptProtectData since 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)
+{
+    TRACE("called\n");
+
+    SetLastError(ERROR_SUCCESS);
+
+    if (!pDataIn || !pDataOut)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return FALSE;
+    }
+
+    if (WINE_TRACE_ON(crypt)) {
+        crypt_report_func_input(pDataIn,pOptionalEntropy,pPromptStruct,dwFlags);
+        wine_dbg_printf("\tppszDataDescr: 0x%x\n",(unsigned int)ppszDataDescr);
+    }
+
+    if (crypt_find_plain_by_cipher(pDataOut, ppszDataDescr, pDataIn, pOptionalEntropy))
+    {
+        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);
+            hexprint("pbData", pDataOut->pbData, pDataOut->cbData);
+        }
+        return TRUE;
+    }
+    
+    SetLastError(ERROR_INVALID_DATA);
+    return FALSE;
+}
+
+
--- /dev/null	2005-02-11 19:52:11.491673144 -0800
+++ 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-02-11 19:52:11.491673144 -0800
+++ dlls/crypt32/tests/protectdata.c	2005-04-06 08:13:46.127330508 -0700
@@ -0,0 +1,241 @@
+/*
+ * 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;
+
+#ifndef STANDALONE
+    printf(" ***\n");
+    printf(" *** Ignore the 'fixme' reminders ... they are intentional.\n");
+    printf(" ***\n");
+#endif
+
+    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