/* * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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 "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(crypt); #define CRYPT32_PROTECTDATA_PROV PROV_RSA_FULL #define CRYPT32_PROTECTDATA_HASH_CALG CALG_SHA1 #define CRYPT32_PROTECTDATA_HASH_LEN 160 #define CRYPT32_PROTECTDATA_KEY_CALG CALG_3DES #define CRYPT32_PROTECTDATA_KEY_LEN 168 #define CRYPT32_PROTECTDATA_SALT_LEN 16 static const BYTE crypt32_protectdata_secret[] = { 'I','\'','m',' ','h','u','n','t','i','n','g',' ', 'w','a','b','b','i','t','s',0 }; /* * 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 - persistent across invocations, ... reboots, password changes, and users DWORD count1; - how many "info1_*[16]" blocks follow (was always 1) BYTE info1_0[16]; - unknown information - unique to each user, but ... persistent across reboots and password changes DWORD null0; - NULL "end of records"? DWORD str_len; - byte length of WCHAR string including term BYTE str[str_len]; - The "dataDescription" value as a NULL-terminated little-endian WCHAR string ALG_ID cipher_alg; - cipher algo - was CALG_3DES DWORD cipher_key_len; - cipher key bit length - was 0xa8==168 DWORD data_len; - length of data (was 16 in samples) BYTE data[data_len]; - unknown data (fingerprint?) DWORD null1; - NULL ? ALG_ID hash_alg; - hash algo - was CALG_SHA1 DWORD hash_len; - bit length of hash - was 0xa0==160 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 */ ALG_ID cipher_alg; DWORD cipher_key_len; DATA_BLOB data0; DWORD null1; ALG_ID hash_alg; DWORD hash_len; 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(const 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 @ %p:%s\n", #blob ,(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(const 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(const 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(const 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 = CryptMemAlloc( len*width))) { return FALSE; } memcpy(*data,&(ptr[*index]),len*width); if (stored) { *stored = len; } *index+=len*width; return TRUE; } static BOOL serialize(const 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);*/ /* cipher_alg */ serialize_dword(pInfo->cipher_alg,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* cipher_key_len */ serialize_dword(pInfo->cipher_key_len,&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);*/ /* hash_alg */ serialize_dword(pInfo->hash_alg,&ptr); /*TRACE("used %u\n",ptr-pSerial->pbData);*/ /* hash_len */ serialize_dword(pInfo->hash_len,&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!? expected %u\n", dwStruct); LocalFree(pSerial->pbData); pSerial->pbData=NULL; pSerial->cbData=0; return FALSE; } return TRUE; } static BOOL unserialize(const 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; } /* cipher_alg */ if (!unserialize_dword(ptr,&index,size,&pInfo->cipher_alg)) { ERR("reading cipher_alg failed!\n"); return FALSE; } /* cipher_key_len */ if (!unserialize_dword(ptr,&index,size,&pInfo->cipher_key_len)) { ERR("reading cipher_key_len 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; } /* hash_alg */ if (!unserialize_dword(ptr,&index,size,&pInfo->hash_alg)) { ERR("reading hash_alg failed!\n"); return FALSE; } /* hash_len */ if (!unserialize_dword(ptr,&index,size,&pInfo->hash_len)) { ERR("reading hash_len 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", index, size); status=FALSE; } return status; } /* perform sanity checks */ static BOOL valid_protect_data(const 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( (LPCSTR)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; CryptMemFree(pInfo->info0.pbData); CryptMemFree(pInfo->info1.pbData); CryptMemFree(pInfo->szDataDescr); CryptMemFree(pInfo->data0.pbData); CryptMemFree(pInfo->salt.pbData); CryptMemFree(pInfo->cipher.pbData); CryptMemFree(pInfo->fingerprint.pbData); } /* copies a string into a data blob */ static BYTE *convert_str_to_blob(LPCSTR str, DATA_BLOB *blob) { if (!str || !blob) return NULL; blob->cbData=strlen(str)+1; if (!(blob->pbData=CryptMemAlloc(blob->cbData))) { blob->cbData=0; } else { strcpy((LPSTR)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(crypt_magic_str, &pInfo->info0); pInfo->count1=0x0001; convert_str_to_blob(crypt_magic_str, &pInfo->info1); pInfo->null0=0x0000; if ((pInfo->szDataDescr=CryptMemAlloc((dwStrLen+1)*sizeof(WCHAR)))) { memcpy(pInfo->szDataDescr,szDataDescr,(dwStrLen+1)*sizeof(WCHAR)); } pInfo->cipher_alg=CRYPT32_PROTECTDATA_KEY_CALG; pInfo->cipher_key_len=CRYPT32_PROTECTDATA_KEY_LEN; convert_str_to_blob(crypt_magic_str, &pInfo->data0); pInfo->null1=0x0000; pInfo->hash_alg=CRYPT32_PROTECTDATA_HASH_CALG; pInfo->hash_len=CRYPT32_PROTECTDATA_HASH_LEN; /* allocate memory to hold a salt */ if ((pInfo->salt.pbData=CryptMemAlloc(CRYPT32_PROTECTDATA_SALT_LEN))) { /* generate random salt */ if (!CryptGenRandom(hProv, CRYPT32_PROTECTDATA_SALT_LEN, pInfo->salt.pbData)) { ERR("CryptGenRandom\n"); free_protect_data(pInfo); return FALSE; } pInfo->salt.cbData=CRYPT32_PROTECTDATA_SALT_LEN; /* 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=CryptMemAlloc(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"); CryptMemFree(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, const 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; } CryptMemFree(one.pbData); return rc; } /* create an encryption key from a given salt and optional entropy */ static BOOL load_encryption_key(HCRYPTPROV hProv, DWORD key_len, const DATA_BLOB *salt, const 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_INSUFFICIENT_BUFFER && dwUsernameLen && (szUsername = CryptMemAlloc(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,(LPBYTE)szUsername,dwUsernameLen,0)) || !CryptHashData(hSaltHash,crypt32_protectdata_secret, sizeof(crypt32_protectdata_secret)-1,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,key_len << 16 | CRYPT_EXPORTABLE,phKey)) { ERR("CryptDeriveKey\n"); rc = FALSE; } /* clean up */ CryptDestroyHash(hSaltHash); CryptMemFree(szUsername); return rc; } /* debugging tool to print the structures of a ProtectData call */ static void report(const DATA_BLOB* pDataIn, const DATA_BLOB* pOptionalEntropy, CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct, DWORD dwFlags) { TRACE("pPromptStruct: %p\n", pPromptStruct); if (pPromptStruct) { TRACE(" cbSize: 0x%x\n", pPromptStruct->cbSize); TRACE(" dwPromptFlags: 0x%x\n", pPromptStruct->dwPromptFlags); TRACE(" hwndApp: %p\n", pPromptStruct->hwndApp); TRACE(" szPrompt: %p %s\n", pPromptStruct->szPrompt, pPromptStruct->szPrompt ? debugstr_w(pPromptStruct->szPrompt) : ""); } TRACE("dwFlags: 0x%04x\n", dwFlags); TRACE_DATA_BLOB(pDataIn); if (pOptionalEntropy) { TRACE_DATA_BLOB(pOptionalEntropy); TRACE(" %s\n",debugstr_an((LPCSTR)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) { static const WCHAR empty_str[1]; 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: %p %s\n", szDataDescr, szDataDescr ? debugstr_w(szDataDescr) : ""); /* Windows appears to create an empty szDataDescr instead of maintaining * a NULL */ if (!szDataDescr) szDataDescr = empty_str; /* get crypt context */ if (!CryptAcquireContextW(&hProv,NULL,MS_ENHANCED_PROV_W,CRYPT32_PROTECTDATA_PROV,CRYPT_VERIFYCONTEXT)) { 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.cipher_key_len,&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", dwLength); /* copy plain text into cipher area for CryptEncrypt call */ protect_data.cipher.cbData=dwLength; if (!(protect_data.cipher.pbData=CryptMemAlloc( protect_data.cipher.cbData))) { ERR("CryptMemAlloc\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", 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 occurred, 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; } if (!pDataIn->cbData) { SetLastError(ERROR_INVALID_DATA); goto finished; } /* debug: show our arguments */ report(pDataIn,pOptionalEntropy,pPromptStruct,dwFlags); TRACE("\tppszDataDescr: %p\n", 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,MS_ENHANCED_PROV_W,CRYPT32_PROTECTDATA_PROV,CRYPT_VERIFYCONTEXT)) { ERR("CryptAcquireContextW failed\n"); goto free_protect_data; } /* load key */ if (!load_encryption_key(hProv,protect_data.cipher_key_len,&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("CryptMemAlloc\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 occurred, 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; }