/* * Copyright 2005 Kees Cook * * 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 #include #include #include #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 const char * hex_str(unsigned char *p, int n) { const char * ptr; char report[80]; int r=-1; report[0]='\0'; ptr = wine_dbg_sprintf("%s",""); while (--n >= 0) { if (r++ % 20 == 19) { ptr = wine_dbg_sprintf("%s%s",ptr,report); report[0]='\0'; } sprintf(report+strlen(report),"%s%02x", r ? "," : "", *p++); } return wine_dbg_sprintf("%s%s",ptr,report); } #define TRACE_DATA_BLOB(blob) do { \ TRACE("%s cbData: %u\n", #blob ,(unsigned int)((blob)->cbData)); \ TRACE("%s pbData @ 0x%x:%s\n", #blob ,(unsigned int)((blob)->pbData), \ hex_str((blob)->pbData, (blob)->cbData)); \ } while (0) static void serialize_dword(DWORD value,BYTE ** ptr) { /*TRACE("called\n");*/ memcpy(*ptr,&value,sizeof(DWORD)); *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) { /*TRACE("called\n");*/ if (!ptr || !index || !value) return FALSE; if (*index+sizeof(DWORD)>size) { return FALSE; } memcpy(value,&(ptr[*index]),sizeof(DWORD)); *index+=sizeof(DWORD); return TRUE; } static BOOL unserialize_string(BYTE * ptr, DWORD *index, DWORD size, DWORD len, DWORD width, BOOL inline_len, BYTE ** data, DWORD * stored) { /*TRACE("called\n");*/ if (!ptr || !data) return FALSE; if (inline_len) { if (!unserialize_dword(ptr,index,size,&len)) return FALSE; } if (*index+len*width>size) { return FALSE; } if (!(*data = HeapAlloc( GetProcessHeap(), 0, len*width))) { return FALSE; } memcpy(*data,&(ptr[*index]),len*width); if (stored) { *stored = len; } *index+=len*width; return TRUE; } static BOOL serialize(struct protect_data_t * pInfo, DATA_BLOB * pSerial) { BYTE * ptr; DWORD dwStrLen; DWORD dwStruct; TRACE("called\n"); if (!pInfo || !pInfo->szDataDescr || !pSerial || !pInfo->info0.pbData || !pInfo->info1.pbData || !pInfo->data0.pbData || !pInfo->salt.pbData || !pInfo->cipher.pbData || !pInfo->fingerprint.pbData) { return FALSE; } if (pInfo->info0.cbData!=16) { ERR("protect_data_t info0 not 16 bytes long\n"); } if (pInfo->info1.cbData!=16) { ERR("protect_data_t info1 not 16 bytes long\n"); } dwStrLen=lstrlenW(pInfo->szDataDescr); pSerial->cbData=0; pSerial->cbData+=sizeof(DWORD)*8; /* 8 raw DWORDs */ pSerial->cbData+=sizeof(DWORD)*4; /* 4 BLOBs with size */ pSerial->cbData+=pInfo->info0.cbData; pSerial->cbData+=pInfo->info1.cbData; pSerial->cbData+=(dwStrLen+1)*sizeof(WCHAR) + 4; /* str, null, size */ pSerial->cbData+=pInfo->data0.cbData; pSerial->cbData+=pInfo->salt.cbData; pSerial->cbData+=pInfo->cipher.cbData; pSerial->cbData+=pInfo->fingerprint.cbData; /* save the actual structure size */ dwStruct = pSerial->cbData; /* There may be a 256 byte minimum, but I can't prove it. */ /*if (pSerial->cbData<256) pSerial->cbData=256;*/ pSerial->pbData=LocalAlloc(LPTR,pSerial->cbData); if (!pSerial->pbData) return FALSE; ptr=pSerial->pbData; /* count0 */ serialize_dword(pInfo->count0,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* info0 */ serialize_string(pInfo->info0.pbData,&ptr, pInfo->info0.cbData,sizeof(BYTE),FALSE); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* count1 */ serialize_dword(pInfo->count1,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* info1 */ serialize_string(pInfo->info1.pbData,&ptr, pInfo->info1.cbData,sizeof(BYTE),FALSE); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* null0 */ serialize_dword(pInfo->null0,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* szDataDescr */ serialize_string((BYTE*)pInfo->szDataDescr,&ptr, (dwStrLen+1)*sizeof(WCHAR),sizeof(BYTE),TRUE); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* unknown0 */ serialize_dword(pInfo->unknown0,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* unknown1 */ serialize_dword(pInfo->unknown1,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* data0 */ serialize_string(pInfo->data0.pbData,&ptr, pInfo->data0.cbData,sizeof(BYTE),TRUE); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* null1 */ serialize_dword(pInfo->null1,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* unknown2 */ serialize_dword(pInfo->unknown2,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* unknown3 */ serialize_dword(pInfo->unknown3,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* salt */ serialize_string(pInfo->salt.pbData,&ptr, pInfo->salt.cbData,sizeof(BYTE),TRUE); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* cipher */ serialize_string(pInfo->cipher.pbData,&ptr, pInfo->cipher.cbData,sizeof(BYTE),TRUE); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* fingerprint */ serialize_string(pInfo->fingerprint.pbData,&ptr, pInfo->fingerprint.cbData,sizeof(BYTE),TRUE); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ if (ptr - pSerial->pbData != dwStruct) { ERR("struct size changed!? %u != expected %u\n", ptr - pSerial->pbData, (unsigned int)dwStruct); LocalFree(pSerial->pbData); pSerial->pbData=NULL; pSerial->cbData=0; return FALSE; } return TRUE; } static BOOL unserialize(DATA_BLOB * pSerial, struct protect_data_t * pInfo) { BYTE * ptr; DWORD index; DWORD size; BOOL status=TRUE; TRACE("called\n"); if (!pInfo || !pSerial || !pSerial->pbData) return FALSE; index=0; ptr=pSerial->pbData; size=pSerial->cbData; /* count0 */ if (!unserialize_dword(ptr,&index,size,&pInfo->count0)) { ERR("reading count0 failed!\n"); return FALSE; } /* info0 */ if (!unserialize_string(ptr,&index,size,16,sizeof(BYTE),FALSE, &pInfo->info0.pbData, &pInfo->info0.cbData)) { ERR("reading info0 failed!\n"); return FALSE; } /* count1 */ if (!unserialize_dword(ptr,&index,size,&pInfo->count1)) { ERR("reading count1 failed!\n"); return FALSE; } /* info1 */ if (!unserialize_string(ptr,&index,size,16,sizeof(BYTE),FALSE, &pInfo->info1.pbData, &pInfo->info1.cbData)) { ERR("reading info1 failed!\n"); return FALSE; } /* null0 */ if (!unserialize_dword(ptr,&index,size,&pInfo->null0)) { ERR("reading null0 failed!\n"); return FALSE; } /* szDataDescr */ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE, (BYTE**)&pInfo->szDataDescr, NULL)) { ERR("reading szDataDescr failed!\n"); return FALSE; } /* unknown0 */ if (!unserialize_dword(ptr,&index,size,&pInfo->unknown0)) { ERR("reading unknown0 failed!\n"); return FALSE; } /* unknown1 */ if (!unserialize_dword(ptr,&index,size,&pInfo->unknown1)) { ERR("reading unknown1 failed!\n"); return FALSE; } /* data0 */ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE, &pInfo->data0.pbData, &pInfo->data0.cbData)) { ERR("reading data0 failed!\n"); return FALSE; } /* null1 */ if (!unserialize_dword(ptr,&index,size,&pInfo->null1)) { ERR("reading null1 failed!\n"); return FALSE; } /* unknown2 */ if (!unserialize_dword(ptr,&index,size,&pInfo->unknown2)) { ERR("reading unknown2 failed!\n"); return FALSE; } /* unknown3 */ if (!unserialize_dword(ptr,&index,size,&pInfo->unknown3)) { ERR("reading unknown3 failed!\n"); return FALSE; } /* salt */ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE, &pInfo->salt.pbData, &pInfo->salt.cbData)) { ERR("reading salt failed!\n"); return FALSE; } /* cipher */ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE, &pInfo->cipher.pbData, &pInfo->cipher.cbData)) { ERR("reading cipher failed!\n"); return FALSE; } /* fingerprint */ if (!unserialize_string(ptr,&index,size,0,sizeof(BYTE),TRUE, &pInfo->fingerprint.pbData, &pInfo->fingerprint.cbData)) { ERR("reading fingerprint failed!\n"); return FALSE; } /* allow structure size to be too big (since some applications * will pad this up to 256 bytes, it seems) */ if (index>size) { /* this is an impossible-to-reach test, but if the padding * issue is ever understood, this may become more useful */ ERR("loaded corrupt structure! (used %u expected %u)\n", (unsigned int)index, (unsigned int)size); status=FALSE; } return status; } /* perform sanity checks */ static BOOL valid_protect_data(struct protect_data_t * pInfo) { BOOL status=TRUE; TRACE("called\n"); if (pInfo->count0 != 0x0001) { ERR("count0 != 0x0001 !\n"); status=FALSE; } if (pInfo->count1 != 0x0001) { ERR("count0 != 0x0001 !\n"); status=FALSE; } if (pInfo->null0 != 0x0000) { ERR("null0 != 0x0000 !\n"); status=FALSE; } if (pInfo->null1 != 0x0000) { ERR("null1 != 0x0000 !\n"); status=FALSE; } /* since we have no idea what info0 is used for, and it seems * rather constant, we can test for a Wine-specific magic string * there to be reasonably sure we're using data created by the Wine * implementation of CryptProtectData. */ if (pInfo->info0.cbData!=strlen(crypt_magic_str)+1 || strcmp(pInfo->info0.pbData,crypt_magic_str) != 0) { ERR("info0 magic value not matched !\n"); status=FALSE; } if (!status) { ERR("unrecognized CryptProtectData block\n"); } return status; } static void free_protect_data(struct protect_data_t * pInfo) { TRACE("called\n"); if (!pInfo) return; if (pInfo->info0.pbData) HeapFree( GetProcessHeap(), 0, pInfo->info0.pbData); if (pInfo->info1.pbData) HeapFree( GetProcessHeap(), 0, pInfo->info1.pbData); if (pInfo->szDataDescr) HeapFree( GetProcessHeap(), 0, pInfo->szDataDescr); if (pInfo->data0.pbData) HeapFree( GetProcessHeap(), 0, pInfo->data0.pbData); if (pInfo->salt.pbData) HeapFree( GetProcessHeap(), 0, pInfo->salt.pbData); if (pInfo->cipher.pbData) HeapFree( GetProcessHeap(), 0, pInfo->cipher.pbData); if (pInfo->fingerprint.pbData) HeapFree( GetProcessHeap(), 0, pInfo->fingerprint.pbData); } /* copies a string into a data blob */ static BYTE * convert_str_to_blob(char* str, DATA_BLOB* blob) { if (!str || !blob) return NULL; blob->cbData=strlen(str)+1; if (!(blob->pbData=HeapAlloc(GetProcessHeap(),0,blob->cbData))) { blob->cbData=0; } else { strcpy(blob->pbData, str); } return blob->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; convert_str_to_blob((char*)crypt_magic_str,&pInfo->info0); pInfo->count1=0x0001; convert_str_to_blob((char*)crypt_magic_str,&pInfo->info1); 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; convert_str_to_blob((char*)crypt_magic_str,&pInfo->data0); 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 */ TRACE_DATA_BLOB(&pInfo->salt); 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 report(DATA_BLOB* pDataIn, DATA_BLOB* pOptionalEntropy, CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags) { TRACE("pPromptStruct: 0x%x\n",(unsigned int)pPromptStruct); if (pPromptStruct) { TRACE(" cbSize: 0x%x\n",(unsigned int)pPromptStruct->cbSize); TRACE(" dwPromptFlags: 0x%x\n",(unsigned int)pPromptStruct->dwPromptFlags); TRACE(" hwndApp: 0x%x\n",(unsigned int)pPromptStruct->hwndApp); TRACE(" szPrompt: 0x%x %s\n", (unsigned int)pPromptStruct->szPrompt, pPromptStruct->szPrompt ? debugstr_w(pPromptStruct->szPrompt) : ""); } TRACE("dwFlags: 0x%04x\n",(unsigned int)dwFlags); TRACE_DATA_BLOB(pDataIn); if (pOptionalEntropy) { TRACE_DATA_BLOB(pOptionalEntropy); TRACE(" %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 * 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 */ report(pDataIn,pOptionalEntropy,pPromptStruct,dwFlags); TRACE("\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 */ TRACE_DATA_BLOB(&protect_data.cipher); /* 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); } if (rc) { SetLastError(ERROR_SUCCESS); TRACE_DATA_BLOB(pDataOut); } TRACE("returning %s\n", rc ? "ok" : "FAIL"); 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; const char * announce_bad_opaque_data = "CryptUnprotectData received a DATA_BLOB that seems to have NOT been generated by Wine. Please enable tracing ('export WINEDEBUG=crypt') to see details."; TRACE("called\n"); SetLastError(ERROR_SUCCESS); if (!pDataIn || !pDataOut) { SetLastError(ERROR_INVALID_PARAMETER); goto finished; } /* debug: show our arguments */ report(pDataIn,pOptionalEntropy,pPromptStruct,dwFlags); TRACE("\tppszDataDescr: 0x%x\n",(unsigned int)ppszDataDescr); /* take apart the opaque blob */ if (!unserialize(pDataIn, &protect_data)) { SetLastError(ERROR_INVALID_DATA); FIXME("%s\n",announce_bad_opaque_data); goto finished; } /* perform basic validation on the resulting structure */ if (!valid_protect_data(&protect_data)) { SetLastError(ERROR_INVALID_DATA); FIXME("%s\n",announce_bad_opaque_data); 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); } if (rc) { SetLastError(ERROR_SUCCESS); if (ppszDataDescr) { TRACE("szDataDescr: %s\n",debugstr_w(*ppszDataDescr)); } TRACE_DATA_BLOB(pDataOut); } TRACE("returning %s\n", rc ? "ok" : "FAIL"); return rc; }