| /* |
| * 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 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; |
| } |