/*
 * 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., 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 "crypt32_private.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;
}
