/////////////////////////////////////////////////////////////////////////////
//  FILE          : nt_hash.c                                              //
//  DESCRIPTION   : Crypto CP interfaces:                                  //
//                  CPBeginHash                                            //
//                  CPUpdateHash                                           //
//                  CPDestroyHash                                          //
//  AUTHOR        :                                                        //
//  HISTORY       :                                                        //
//      Jan 25 1995 larrys  Changed from Nametag                           //
//      Feb 23 1995 larrys  Changed NTag_SetLastError to SetLastError      //
//      May  8 1995 larrys  Changes for MAC hashing                        //
//      May 10 1995 larrys  added private api calls                        //
//      Jul 13 1995 larrys  Changed MAC stuff                              //
//      Aug 07 1995 larrys  Added Auto-Inflate to CryptBeginHash           //
//      Aug 30 1995 larrys  Removed RETURNASHVALUE from CryptGetHashValue  //
//      Sep 19 1995 larrys  changed USERDATA to CRYPT_USERDATA             //
//      Oct 03 1995 larrys  check for 0 on Createhash for hKey             //
//      Oct 05 1995 larrys  Changed HashSessionKey to hash key material    //
//      Oct 13 1995 larrys  Removed CPGetHashValue                         //
//      Oct 17 1995 larrys  Added MD2                                      //
//      Nov  3 1995 larrys  Merge for NT checkin                           //
//      Nov 14 1995 larrys  Fixed memory leak                              //
//      Mar 01 1996 rajeshk Added check for Hash Values                    //
//      May 15 1996 larrys  Changed NTE_NO_MEMORY to ERROR_NOT_ENOUGHT...  //
//      Jun  6 1996 a-johnb Added support for SSL 3.0 signatures           //
//      Apr 25 1997 jeffspel Fix for Bug 76393, GPF on pbData = NULL       //
//      May 23 1997 jeffspel Added provider type checking                  //
//                                                                         //
//  Copyright (C) 1993 Microsoft Corporation   All Rights Reserved         //
/////////////////////////////////////////////////////////////////////////////

#include "precomp.h"
#include "nt_rsa.h"
#include "tripldes.h"
#include "mac.h"
#include "ssl3.h"
#include "aes.h"

extern BOOL
FIsLegalKey(
    PNTAGUserList pTmpUser,
    PNTAGKeyList pKey,
    BOOL fRC2BigKeyOK);

extern DWORD
InflateKey(
    IN PNTAGKeyList pTmpKey);

extern DWORD
BlockEncrypt(
    void EncFun(BYTE *In, BYTE *Out, void *key, int op),
    PNTAGKeyList pKey,
    int BlockLen,
    BOOL Final,
    BYTE  *pbData,
    DWORD *pdwDataLen,
    DWORD dwBufLen);

extern DWORD
LocalGetHashVal(
    IN ALG_ID Algid,
    IN DWORD dwHashFlags,
    IN OUT BYTE *pbHashData,
    OUT BYTE *pbHashVal,
    OUT DWORD *pcbHashVal);

#ifdef CSP_USE_MD5
//
// Function : TestMD5
//
// Description : This function hashes the passed in message with the MD5 hash
//               algorithm and returns the resulting hash value.
//

BOOL
TestMD5(
    BYTE *pbMsg,
    DWORD cbMsg,
    BYTE *pbHash)
{
    MD5_CTX MD5;
    BOOL    fRet = FALSE;

    // Check length for input data
    if (0 == cbMsg)
        goto ErrorExit;

    // Initialize MD5
    MD5Init(&MD5);

    // Compute MD5
    MD5Update(&MD5, pbMsg, cbMsg);

    MD5Final(&MD5);
    memcpy(pbHash, MD5.digest, MD5DIGESTLEN);

    fRet = TRUE;

ErrorExit:
    return fRet;
}
#endif // CSP_USE_MD5

#ifdef CSP_USE_SHA1
//
// Function : TestSHA1
//
// Description : This function hashes the passed in message with the SHA1 hash
//               algorithm and returns the resulting hash value.
//

BOOL
TestSHA1(
    BYTE *pbMsg,
    DWORD cbMsg,
    BYTE *pbHash)
{
    A_SHA_CTX   HashContext;
    BOOL        fRet = FALSE;

    // Check length for input data
    if (0 == cbMsg)
        goto ErrorExit;

    // Initialize SHA
    A_SHAInit(&HashContext);

    // Compute SHA
    A_SHAUpdate(&HashContext, pbMsg, cbMsg);

    A_SHAFinal(&HashContext, pbHash);

    fRet = TRUE;

ErrorExit:
    return fRet;
}
#endif // CSP_USE_SHA1

BOOL
ValidHashAlgid(
    PNTAGUserList pTmpUser,
    ALG_ID Algid)
{
    if ((PROV_RSA_SCHANNEL == pTmpUser->dwProvType) &&
        ((CALG_MD2 == Algid) || (CALG_MD4 == Algid)))
        return FALSE;
    else
        return TRUE;
}

// local function for creating hashes
DWORD
LocalCreateHash(
    IN ALG_ID Algid,
    OUT BYTE **ppbHashData,
    OUT DWORD *pcbHashData)
{
    DWORD dwReturn = ERROR_INTERNAL_ERROR;

    switch (Algid)
    {
#ifdef CSP_USE_MD2
    case CALG_MD2:
    {
        MD2_object *pMD2Hash;

        pMD2Hash = (MD2_object *)_nt_malloc(sizeof(MD2_object));
        if (NULL == pMD2Hash)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        // Set up the Initial MD2 Hash State
        memset ((BYTE *)pMD2Hash, 0, sizeof(MD2_object));
        pMD2Hash->FinishFlag = FALSE;

        *pcbHashData = sizeof(MD2_object);
        *ppbHashData = (LPBYTE)pMD2Hash;
        break;
    }
#endif

#ifdef CSP_USE_MD4
    case CALG_MD4:
    {
        MD4_object *pMD4Hash;

        pMD4Hash = (MD4_object *)_nt_malloc(sizeof(MD4_object));
        if (NULL == pMD4Hash)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        // Set up the Initial MD4 Hash State
        memset ((BYTE *)pMD4Hash, 0, sizeof(MD4_object));
        pMD4Hash->FinishFlag = FALSE;
        MDbegin(&pMD4Hash->MD);

        *pcbHashData = sizeof(MD4_object);
        *ppbHashData = (BYTE*)pMD4Hash;
        break;
    }
#endif

#ifdef CSP_USE_MD5
    case CALG_MD5:
    {
        MD5_object *pMD5Hash;

        pMD5Hash = (MD5_object *)_nt_malloc(sizeof(MD5_object));
        if (NULL == pMD5Hash)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        // Set up the our state
        pMD5Hash->FinishFlag = FALSE;
        MD5Init(pMD5Hash);

        *ppbHashData = (BYTE*)pMD5Hash;
        *pcbHashData = sizeof(MD5_object);
        break;
    }
#endif

#ifdef CSP_USE_SHA
    case CALG_SHA:
    {
        A_SHA_CTX *pSHAHash;

        pSHAHash = (A_SHA_CTX *)_nt_malloc(sizeof(A_SHA_CTX));
        if (NULL == pSHAHash)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        // Set up our state
        A_SHAInit(pSHAHash);
        pSHAHash->FinishFlag = FALSE;

        *ppbHashData = (BYTE*)pSHAHash;
        *pcbHashData = sizeof(A_SHA_CTX);
        break;
    }
#endif

    default:
        dwReturn = (DWORD)NTE_BAD_ALGID;
        goto ErrorExit;
    }

    dwReturn = ERROR_SUCCESS;

ErrorExit:
    return dwReturn;
}


/*
 -  CPBeginHash
 -
 *  Purpose:
 *                initate the hashing of a stream of data
 *
 *
 *  Parameters:
 *               IN  hUID    -  Handle to the user identifcation
 *               IN  Algid   -  Algorithm identifier of the hash algorithm
 *                              to be used
 *               IN  hKey    -  Optional key for MAC algorithms
 *               IN  dwFlags -  Flags values
 *               OUT pHash   -  Handle to hash object
 *
 *  Returns:
 */

BOOL WINAPI
CPCreateHash(
    IN HCRYPTPROV hUID,
    IN ALG_ID Algid,
    IN HCRYPTKEY hKey,
    IN DWORD dwFlags,
    OUT HCRYPTHASH *phHash)
{
    DWORD           dwReturn = ERROR_INTERNAL_ERROR;
    PNTAGUserList   pTmpUser;
    PNTAGHashList   pCurrentHash = NULL;
    PNTAGKeyList    pTmpKey;
#ifdef CSP_USE_SSL3
    PSCH_HASH       pSChHash;
#endif // CSP_USE_SSL3
    BOOL            fRet;
    DWORD           dwSts;

    EntryPoint
    if (dwFlags != 0)
    {
        dwReturn = (DWORD)NTE_BAD_FLAGS;
        goto ErrorExit;
    }

    // check if the user handle is valid
    pTmpUser = NTLCheckList(hUID, USER_HANDLE);
    if (NULL == pTmpUser)
    {
        dwReturn = (DWORD)NTE_BAD_UID;
        goto ErrorExit;
    }

    if (!ValidHashAlgid(pTmpUser, Algid))
    {
        dwReturn = (DWORD)NTE_BAD_ALGID;
        goto ErrorExit;
    }

    // Prepare the structure to be used as the hash handle
    pCurrentHash = (PNTAGHashList)_nt_malloc(sizeof(NTAGHashList));
    if (NULL == pCurrentHash)
    {
        dwReturn = ERROR_NOT_ENOUGH_MEMORY;
        goto ErrorExit;
    }

    memset(pCurrentHash, 0, sizeof(NTAGHashList));
    pCurrentHash->Algid = Algid;
    pCurrentHash->hUID = hUID;

    // determine which hash algorithm is to be used
    switch (Algid)
    {
#ifdef CSP_USE_MAC
    case CALG_MAC:
    {
        MACstate *pMACVal;

        if (hKey == 0)
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }

        dwSts = NTLValidate(hKey, hUID, KEY_HANDLE, &pTmpKey);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_KEY : dwSts;
            goto ErrorExit;
        }

        if (pTmpKey->Mode != CRYPT_MODE_CBC)
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }

        // Check if we should do an auto-inflate
        if (pTmpKey->pData == NULL)
        {
            dwSts = InflateKey(pTmpKey);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
        }

        pMACVal = (MACstate *)_nt_malloc(sizeof(MACstate));
        if (NULL == pMACVal)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        pCurrentHash->pHashData = pMACVal;
        pCurrentHash->dwDataLen = sizeof(MACstate);
        pCurrentHash->hKey = hKey;
        pMACVal->dwBufLen = 0;
        pMACVal->FinishFlag = FALSE;
        break;
    }
#endif

    case CALG_HMAC:
    {
        if (hKey == 0)
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }

        dwSts = NTLValidate(hKey, hUID, KEY_HANDLE, &pTmpKey);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = (NTE_FAIL == dwSts) ? (DWORD)NTE_BAD_KEY : dwSts;
            goto ErrorExit;
        }

        pCurrentHash->hKey = hKey;
        break;
    }

#ifdef CSP_USE_SSL3SHAMD5
    case CALG_SSL3_SHAMD5:
    {
        pCurrentHash->pHashData = _nt_malloc(SSL3_SHAMD5_LEN);
        if (NULL == pCurrentHash->pHashData)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        pCurrentHash->dwDataLen = SSL3_SHAMD5_LEN;
        break;
    }
#endif

#ifdef CSP_USE_SSL3
    case CALG_SCHANNEL_MASTER_HASH:
    {
        if (0 == hKey)
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }

        dwSts = NTLValidate(hKey, hUID, KEY_HANDLE, &pTmpKey);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_KEY : dwSts;
            goto ErrorExit;
        }

        if ((CALG_SSL3_MASTER != pTmpKey->Algid) &&
            (CALG_PCT1_MASTER != pTmpKey->Algid) &&
            (pTmpKey->cbKeyLen > MAX_PREMASTER_LEN))
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }

        pCurrentHash->dwDataLen = sizeof(SCH_HASH);
        pCurrentHash->pHashData = (BYTE *)_nt_malloc(pCurrentHash->dwDataLen);
        if (NULL == pCurrentHash->pHashData)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }
        memset(pCurrentHash->pHashData, 0, pCurrentHash->dwDataLen);
        pSChHash = (PSCH_HASH)pCurrentHash->pHashData;
        pSChHash->ProtocolAlgid = pTmpKey->Algid;

        dwSts = SChGenMasterKey(pTmpKey, pSChHash);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = dwSts;
            goto ErrorExit;
        }
        break;
    }

    case CALG_TLS1PRF:
    {
        PRF_HASH    *pPRFHash;
        PSCH_KEY    pSChKey;

        if (0 == hKey)
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }

        dwSts = NTLValidate(hKey, hUID, KEY_HANDLE, &pTmpKey);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_KEY : dwSts;
            goto ErrorExit;
        }

        if (CALG_TLS1_MASTER != pTmpKey->Algid)
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }

        // check if the master key is finished
        pSChKey = (PSCH_KEY)pTmpKey->pData;
        if ((!pSChKey->fFinished) || (TLS_MASTER_LEN != pTmpKey->cbKeyLen))
        {
            dwReturn = (DWORD)NTE_BAD_KEY_STATE;
            goto ErrorExit;
        }

        pCurrentHash->dwDataLen = sizeof(PRF_HASH);
        pCurrentHash->pHashData = (BYTE *)_nt_malloc(pCurrentHash->dwDataLen);
        if (NULL == pCurrentHash->pHashData)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }
        memset(pCurrentHash->pHashData, 0, pCurrentHash->dwDataLen);
        pPRFHash = (PRF_HASH*)pCurrentHash->pHashData;
        memcpy(pPRFHash->rgbMasterKey, pTmpKey->pKeyValue, TLS_MASTER_LEN);
        break;
    }
#endif // CSP_USE_SSL3

    default:
        if (hKey != 0)
        {
            dwReturn = NTE_BAD_KEY;
            goto ErrorExit;
        }

        dwSts = LocalCreateHash(Algid, (BYTE**)&pCurrentHash->pHashData,
                                &pCurrentHash->dwDataLen);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = dwSts;
            goto ErrorExit;
        }
    }

    dwSts = NTLMakeItem(phHash, HASH_HANDLE, pCurrentHash);
    if (ERROR_SUCCESS != dwSts)
    {
        dwReturn = dwSts;
        goto ErrorExit;
    }

    dwReturn = ERROR_SUCCESS;

ErrorExit:
    fRet = (ERROR_SUCCESS == dwReturn);
    if (!fRet)
    {
        if (NULL != pCurrentHash)
        {
            if (pCurrentHash->pHashData)
                _nt_free(pCurrentHash->pHashData, pCurrentHash->dwDataLen);
            _nt_free(pCurrentHash, sizeof(NTAGHashList));
        }
        SetLastError(dwReturn);
    }
    return fRet;
}

DWORD
LocalHashData(
    IN ALG_ID Algid,
    IN OUT BYTE *pbHashData,
    IN BYTE *pbData,
    IN DWORD cbData)
{
    DWORD   dwReturn = ERROR_INTERNAL_ERROR;
    BYTE    *ptmp;
    DWORD   BytePos;

    switch (Algid)
    {
#ifdef CSP_USE_MD2
    case CALG_MD2:
    {
        MD2_object *pMD2Hash;

        // make sure the hash is updatable
        pMD2Hash = (MD2_object *)pbHashData;
        if (pMD2Hash->FinishFlag)
        {
            dwReturn = (DWORD)NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        if (0 != MD2Update(&pMD2Hash->MD, pbData, cbData))
        {
            // This is a reasonable return code, since currently
            // the only value MD2Update returns is zero.
            dwReturn = (DWORD)NTE_FAIL;
            goto ErrorExit;
        }

        break;
    }
#endif

#ifdef CSP_USE_MD4
    case CALG_MD4:
    {
        MD4_object *pMD4Hash;
        int nSts;

        pMD4Hash = (MD4_object *)pbHashData;

        // make sure the hash is updatable
        if (pMD4Hash->FinishFlag)
        {
            dwReturn = (DWORD)NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        // MD4 hashes when the size == MD4BLOCKSIZE and finishes the
        // hash when the given size is < MD4BLOCKSIZE.
        // So, ensure that the user always gives a full block here --
        // when NTagFinishHash is called, we'll send the last bit and
        // that'll finish off the hash.

        ptmp = (BYTE *)pbData;
        for (;;)
        {
            // check if there's plenty of room in the buffer
            if (cbData < (MD4BLOCKSIZE - pMD4Hash->BufLen))
            {
                // just append to whatever's already
                memcpy(pMD4Hash->Buf + pMD4Hash->BufLen, ptmp, cbData);

                // set of the trailing buffer length field
                pMD4Hash->BufLen += (BYTE)cbData;
                break;
            }

            // determine what we need to fill the buffer, then do it.
            BytePos = MD4BLOCKSIZE - pMD4Hash->BufLen;
            ASSERT(BytePos <= dwTmpLen);
            memcpy(pMD4Hash->Buf + pMD4Hash->BufLen, ptmp, BytePos);

            // The buffer is now full, process it.
            nSts = MDupdate(&pMD4Hash->MD, pMD4Hash->Buf,
                            MD4BYTESTOBITS(MD4BLOCKSIZE));
            if (MD4_SUCCESS != nSts)
            {
                dwReturn = (DWORD)NTE_FAIL;
                goto ErrorExit;
            }

            // now it's empty.
            pMD4Hash->BufLen = 0;

            // we processed some bytes, so reflect that and try again
            cbData -= BytePos;
            ptmp += BytePos;

            if (cbData == 0)
                break;
        }
        break;
    }
#endif

#ifdef CSP_USE_MD5
    case CALG_MD5:
    {
        MD5_object *pMD5Hash;

        // make sure the hash is updatable
        pMD5Hash = (MD5_object *)pbHashData;
        if (pMD5Hash->FinishFlag)
        {
            dwReturn = (DWORD)NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        MD5Update(pMD5Hash, pbData, cbData);
        break;
    }
#endif

#ifdef CSP_USE_SHA
    case CALG_SHA:
    {
        A_SHA_CTX *pSHAHash;

        // make sure the hash is updatable
        pSHAHash = (A_SHA_CTX *)pbHashData;
        if (pSHAHash->FinishFlag)
        {
            dwReturn = (DWORD) NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        A_SHAUpdate(pSHAHash, pbData, cbData);
        break;
    }
#endif

    default:
        dwReturn = (DWORD)NTE_BAD_ALGID;
        goto ErrorExit;
    }

    dwReturn = ERROR_SUCCESS;

ErrorExit:
    return dwReturn;
}

/*static*/ DWORD
LocalMACData(
    IN HCRYPTPROV hUID,
    IN PNTAGHashList pTmpHash,
    IN CONST BYTE *pbData,
    IN DWORD cbData)
{
    DWORD           dwReturn = ERROR_INTERNAL_ERROR;
    MACstate        *pMAC;
    PNTAGKeyList    pTmpKey;
    BYTE            *pbTmp;
    DWORD           dwTmpLen;
    BYTE            *pb = NULL;
    DWORD           cb;
    DWORD           i;
    BYTE            *pbJunk = NULL;
    DWORD           dwBufSlop;
    DWORD           dwEncLen;
    DWORD           dwSts;
    PBYTE           pbKeyHash = NULL;
    DWORD           cbKeyHash = 0;
    
    dwTmpLen = cbData;
    pbTmp = (BYTE *) pbData;

    switch (pTmpHash->Algid)
    {
#ifdef CSP_USE_MAC
    case CALG_MAC:
    {
        pMAC = (MACstate *)pTmpHash->pHashData;

        // make sure the hash is updatable
        if (pMAC->FinishFlag)
        {
            dwReturn = (DWORD)NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        dwSts = NTLValidate(pTmpHash->hKey, hUID, KEY_HANDLE, &pTmpKey);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_KEY : dwSts;
            goto ErrorExit;
        }

        if (pMAC->dwBufLen + dwTmpLen <= pTmpKey->dwBlockLen)
        {
            memcpy(pMAC->Buffer + pMAC->dwBufLen, pbTmp, dwTmpLen);
            pMAC->dwBufLen += dwTmpLen;
            dwReturn = ERROR_SUCCESS;
            goto ErrorExit;
        }

        memcpy(pMAC->Buffer+pMAC->dwBufLen, pbTmp,
               (pTmpKey->dwBlockLen - pMAC->dwBufLen));

        dwTmpLen -= (pTmpKey->dwBlockLen - pMAC->dwBufLen);
        pbTmp += (pTmpKey->dwBlockLen - pMAC->dwBufLen);

        pMAC->dwBufLen = pTmpKey->dwBlockLen;

        switch (pTmpKey->Algid)
        {
        case CALG_RC2:
            dwSts = BlockEncrypt(RC2, pTmpKey, RC2_BLOCKLEN, FALSE,
                                 pMAC->Buffer,  &pMAC->dwBufLen,
                                 MAX_BLOCKLEN);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
            break;

        case CALG_DES:
            dwSts = BlockEncrypt(des, pTmpKey, DES_BLOCKLEN, FALSE,
                                 pMAC->Buffer,  &pMAC->dwBufLen,
                                 MAX_BLOCKLEN);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
            break;

#ifdef CSP_USE_3DES
        case CALG_3DES_112:
        case CALG_3DES:
            dwSts = BlockEncrypt(tripledes, pTmpKey, DES_BLOCKLEN,
                                 FALSE, pMAC->Buffer, &pMAC->dwBufLen,
                                 MAX_BLOCKLEN);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
            break;
#endif
#ifdef CSP_USE_AES
        case CALG_AES_128:
        case CALG_AES_192:
        case CALG_AES_256:
            dwSts = BlockEncrypt(aes, pTmpKey, pTmpKey->dwBlockLen,
                                 FALSE, pMAC->Buffer, &pMAC->dwBufLen,
                                 MAX_BLOCKLEN);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
            break;
#endif
        }

        pMAC->dwBufLen = 0;

        dwBufSlop = dwTmpLen % pTmpKey->dwBlockLen;
        if (dwBufSlop == 0)
        {
            dwBufSlop = pTmpKey->dwBlockLen;
        }

        pbJunk = _nt_malloc(dwTmpLen);
        if (NULL == pbJunk)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        memcpy(pbJunk, pbTmp, dwTmpLen - dwBufSlop);
        dwEncLen = dwTmpLen - dwBufSlop;

        switch (pTmpKey->Algid)
        {
        case CALG_RC2:
            dwSts = BlockEncrypt(RC2, pTmpKey, RC2_BLOCKLEN, FALSE,
                                 pbJunk,  &dwEncLen, dwTmpLen);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
            break;

        case CALG_DES:
            dwSts = BlockEncrypt(des, pTmpKey, DES_BLOCKLEN, FALSE,
                                 pbJunk,  &dwEncLen, dwTmpLen);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
            break;

#ifdef CSP_USE_3DES
        case CALG_3DES_112:
        case CALG_3DES:
            dwSts = BlockEncrypt(tripledes, pTmpKey, DES_BLOCKLEN,
                                 FALSE, pbJunk,  &dwEncLen, dwTmpLen);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
            break;
#endif

#ifdef CSP_USE_AES
        case CALG_AES_128:
        case CALG_AES_192:
        case CALG_AES_256:
            dwSts = BlockEncrypt(aes, pTmpKey, pTmpKey->dwBlockLen,
                                 FALSE, pbJunk,  &dwEncLen, dwTmpLen);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }
            break;
#endif
        }

        memcpy(pMAC->Buffer, pbTmp + dwEncLen, dwBufSlop);
        pMAC->dwBufLen = dwBufSlop;
        break;
    }
#endif

    case CALG_HMAC:
    {
        if (!(pTmpHash->HMACState & HMAC_STARTED))
        {
            dwSts = NTLValidate(pTmpHash->hKey, hUID,
                                KEY_HANDLE, &pTmpKey);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_KEY : dwSts;
                goto ErrorExit;
            }

            // If key is longer than block length, hash the key 
            // data first
            if (pTmpKey->cbKeyLen > HMAC_DEFAULT_STRING_LEN)
            {
                dwSts = LocalCreateHash(pTmpHash->HMACAlgid, &pbKeyHash, &cbKeyHash);
                if (ERROR_SUCCESS != dwSts)
                {
                    dwReturn = dwSts;
                    goto ErrorExit;
                }

                dwSts = LocalHashData(pTmpHash->HMACAlgid, pbKeyHash, pTmpKey->pKeyValue, 
                                      pTmpKey->cbKeyLen);
                if (ERROR_SUCCESS != dwSts)
                {
                    dwReturn = dwSts;
                    goto ErrorExit;
                }
                
                pb = (BYTE *)_nt_malloc(HMAC_DEFAULT_STRING_LEN);
                if (NULL == pb)
                {
                    dwReturn = ERROR_NOT_ENOUGH_MEMORY;
                    goto ErrorExit;
                }

                cb = HMAC_DEFAULT_STRING_LEN;
                dwSts = LocalGetHashVal(pTmpHash->HMACAlgid, 0, pbKeyHash, pb, &cb);
                if (ERROR_SUCCESS != dwSts)
                {
                    dwReturn = dwSts;
                    goto ErrorExit;
                }

                for (i = 0; i < HMAC_DEFAULT_STRING_LEN; i++)
                    pb[i] ^= (pTmpHash->pbHMACInner)[i];

                cb = HMAC_DEFAULT_STRING_LEN;
            }
            else
            {         
                if (pTmpKey->cbKeyLen < pTmpHash->cbHMACInner)
                    cb = pTmpHash->cbHMACInner;
                else
                    cb = pTmpKey->cbKeyLen;
    
                pb = (BYTE *)_nt_malloc(cb);
                if (NULL == pb)
                {
                    dwReturn = ERROR_NOT_ENOUGH_MEMORY;
                    goto ErrorExit;
                }
                memcpy(pb, pTmpHash->pbHMACInner, pTmpHash->cbHMACInner);
    
                // currently no support for byte reversed keys with HMAC
                for (i=0;i<pTmpKey->cbKeyLen;i++)
                    pb[i] ^= (pTmpKey->pKeyValue)[i];
            }

            dwSts = LocalHashData(pTmpHash->HMACAlgid, pTmpHash->pHashData,
                                  pb, cb);
            if (ERROR_SUCCESS != dwSts)
            {
                dwReturn = dwSts;
                goto ErrorExit;
            }

            pTmpHash->HMACState |= HMAC_STARTED;
            memnuke(pb, cb);
            memnuke(pbKeyHash, cbKeyHash);
        }

        dwSts = LocalHashData(pTmpHash->HMACAlgid, pTmpHash->pHashData,
                              (BYTE*)pbData, cbData);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = dwSts;
            goto ErrorExit;
        }

        break;
    }

    default:
        dwReturn = (DWORD)NTE_BAD_ALGID;
        goto ErrorExit;
    }

    dwReturn = ERROR_SUCCESS;

ErrorExit:
    if (pbJunk)
        _nt_free(pbJunk, dwTmpLen);
    if (pb)
        _nt_free(pb, cb);
    if (pbKeyHash)
        _nt_free(pbKeyHash, cbKeyHash);
    return dwReturn;
}


/*
 -  CPHashData
 -
 *  Purpose:
 *                Compute the cryptograghic hash on a stream of data
 *
 *
 *  Parameters:
 *               IN  hUID      -  Handle to the user identifcation
 *               IN  hHash     -  Handle to hash object
 *               IN  pbData    -  Pointer to data to be hashed
 *               IN  dwDataLen -  Length of the data to be hashed
 *               IN  dwFlags   -  Flags values
 *
 *  Returns:
 */

BOOL WINAPI
CPHashData(
    IN HCRYPTPROV hUID,
    IN HCRYPTHASH hHash,
    IN CONST BYTE *pbData,
    IN DWORD dwDataLen,
    IN DWORD dwFlags)
{
    DWORD           dwReturn = ERROR_INTERNAL_ERROR;
    PNTAGHashList   pTmpHash;
    PNTAGUserList   pUser;
    BOOL            fRet;
    DWORD           dwSts;

    EntryPoint
    if (0 != (dwFlags & ~(CRYPT_USERDATA)))
    {
        dwReturn = (DWORD)NTE_BAD_FLAGS;
        goto ErrorExit;
    }

    pUser = (PNTAGUserList)NTLCheckList(hUID, USER_HANDLE);
    if (NULL == pUser)
    {
        dwReturn = (DWORD)NTE_BAD_UID;
        goto ErrorExit;
    }

    if (0 == dwDataLen)
    {
        dwReturn = ERROR_SUCCESS;
        goto ErrorExit;
    }

    if (NULL == pbData)
    {
        dwReturn = (DWORD)NTE_BAD_DATA;
        goto ErrorExit;
    }

    dwSts = NTLValidate(hHash, hUID, HASH_HANDLE, &pTmpHash);
    if (ERROR_SUCCESS != dwSts)
    {
        // NTLValidate doesn't know what error to set
        // so it set NTE_FAIL -- fix it up.
        dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_HASH : dwSts;
        goto ErrorExit;
    }

    if (pTmpHash->HashFlags & HF_VALUE_SET)
    {
        dwReturn = (DWORD)NTE_BAD_HASH_STATE;
        goto ErrorExit;
    }

    switch (pTmpHash->Algid)
    {
#ifdef CSP_USE_MAC
    case CALG_MAC:
#endif // CSP_USE_MAC
    case CALG_HMAC:
        dwSts = LocalMACData(hUID, pTmpHash, pbData, dwDataLen);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = dwSts;
            goto ErrorExit;
        }
        break;

    default:
        dwSts = LocalHashData(pTmpHash->Algid, pTmpHash->pHashData,
                              (BYTE*)pbData, dwDataLen);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = dwSts;
            goto ErrorExit;
        }
    }

    pTmpHash->dwHashState |= DATA_IN_HASH;

    dwReturn = ERROR_SUCCESS;

ErrorExit:
    fRet = (ERROR_SUCCESS == dwReturn);
    if (!fRet)
        SetLastError(dwReturn);
    return fRet;
}

/*static*/ DWORD
SetupKeyToBeHashed(
    PNTAGKeyList pKey,
    BYTE **ppbData,
    DWORD *pcbData,
    DWORD dwFlags)
{
    DWORD   dwReturn = ERROR_INTERNAL_ERROR;
    DWORD   cb;
    DWORD   i;

    *ppbData = NULL;
    cb = pKey->cbKeyLen;

    *ppbData = (BYTE *)_nt_malloc(cb);
    if (NULL == *ppbData)
    {
        dwReturn = ERROR_NOT_ENOUGH_MEMORY;
        goto ErrorExit;
    }

    if (CRYPT_LITTLE_ENDIAN & dwFlags)
    {
        memcpy(*ppbData, pKey->pKeyValue, cb);
    }
    else
    {
        // Reverse the session key bytes
        for (i = 0; i < cb; i++)
            (*ppbData)[i] = (pKey->pKeyValue)[cb - i - 1];
    }

    *pcbData = cb;
    dwReturn = ERROR_SUCCESS;

ErrorExit:
    return dwReturn;
}


/*
 -      CPHashSessionKey
 -
 *      Purpose:
 *                Compute the cryptograghic hash on a key object.
 *
 *
 *      Parameters:
 *               IN  hUID      -  Handle to the user identifcation
 *               IN  hHash     -  Handle to hash object
 *               IN  hKey      -  Handle to a key object
 *               IN  dwFlags   -  Flags values
 *
 *      Returns:
 *               CRYPT_FAILED
 *               CRYPT_SUCCEED
 */

BOOL WINAPI
CPHashSessionKey(
    IN HCRYPTPROV hUID,
    IN HCRYPTHASH hHash,
    IN HCRYPTKEY hKey,
    IN DWORD dwFlags)
{
    DWORD           dwReturn = ERROR_INTERNAL_ERROR;
    PNTAGHashList   pTmpHash;
    PNTAGKeyList    pTmpKey;
    PNTAGUserList   pTmpUser;
    DWORD           dwDataLen;
    BYTE            *pbData = NULL;
    DWORD           BytePos;
#ifdef CSP_USE_SSL3
    PSCH_KEY        pSChKey;
#endif // CSP_USE_SSL3
    BOOL                fRet;
    DWORD               dwSts;

    EntryPoint
    if (dwFlags & ~(CRYPT_LITTLE_ENDIAN))
    {
        dwReturn = (DWORD)NTE_BAD_FLAGS;
        goto ErrorExit;
    }

    // check the user identification
    pTmpUser = (PNTAGUserList)NTLCheckList(hUID, USER_HANDLE);
    if (NULL == pTmpUser)
    {
        dwReturn = (DWORD)NTE_BAD_UID;
        goto ErrorExit;
    }

    dwSts = NTLValidate(hHash, hUID, HASH_HANDLE, &pTmpHash);
    if (ERROR_SUCCESS != dwSts)
    {
        dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_HASH : dwSts;
        goto ErrorExit;
    }

    if (pTmpHash->HashFlags & HF_VALUE_SET)
    {
        dwReturn = (DWORD)NTE_BAD_HASH_STATE;
        goto ErrorExit;
    }

    dwSts = NTLValidate((HNTAG)hKey, hUID, KEY_HANDLE, &pTmpKey);
    if (ERROR_SUCCESS != dwSts)
    {
        dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_KEY : dwSts;
        goto ErrorExit;
    }

    if (!FIsLegalKey(pTmpUser, pTmpKey, FALSE))
    {
        dwReturn = (DWORD)NTE_BAD_KEY;
        goto ErrorExit;
    }

#ifdef CSP_USE_SSL3
    if ((CALG_SSL3_MASTER == pTmpKey->Algid) ||
        (CALG_TLS1_MASTER == pTmpKey->Algid) ||
        (CALG_PCT1_MASTER == pTmpKey->Algid))
    {
        if (NULL == pTmpKey->pData)
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }
        pSChKey = (PSCH_KEY)pTmpKey->pData;
        if (!pSChKey->fFinished)
        {
            dwReturn = (DWORD)NTE_BAD_KEY;
            goto ErrorExit;
        }
    }
#endif // CSP_USE_SSL3

#if 0
    // Check if we should do an auto-inflate
    if (pTmpKey->pData == NULL)
    {
        if (NTAG_FAILED(CPInflateKey(pTmpKey)))
        {
            dwReturn = GetLastError();
            goto ErrorExit;
        }
    }
#endif

    if ((CALG_DES == pTmpKey->Algid)
        || (CALG_3DES == pTmpKey->Algid)
        || (CALG_3DES_112 == pTmpKey->Algid))
    {
        if (PROV_RSA_SCHANNEL != pTmpUser->dwProvType)
        {
            if ((POLICY_MS_STRONG == pTmpUser->dwCspTypeId) ||
                (!(pTmpUser->Rights & CRYPT_DES_HASHKEY_BACKWARDS)))
            {
                desparityonkey(pTmpKey->pKeyValue, pTmpKey->cbKeyLen);
            }
        }
    }

    dwSts = SetupKeyToBeHashed(pTmpKey, &pbData,
                               &dwDataLen, dwFlags);
    if (ERROR_SUCCESS != dwSts)
    {
        dwReturn = dwSts;
        goto ErrorExit;
    }

    switch (pTmpHash->Algid)
    {
#ifdef CSP_USE_MD2
    case CALG_MD2:
    {
        MD2_object *pMD2Hash;

        pMD2Hash = (MD2_object *)pTmpHash->pHashData;

        // make sure the hash is updatable
        if (pMD2Hash->FinishFlag)
        {
            dwReturn = (DWORD)NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        if (0 != MD2Update(&pMD2Hash->MD, pbData, dwDataLen))
        {
            // This is reasonable, since MD2Update only returns zero.
            dwReturn = (DWORD)NTE_FAIL;
            goto ErrorExit;
        }
        break;
    }
#endif

#ifdef CSP_USE_MD4
    case CALG_MD4:
    {
        MD4_object *pMD4Hash;
        int nSts;

        pMD4Hash = (MD4_object *)pTmpHash->pHashData;

        // make sure the hash is updatable
        if (pMD4Hash->FinishFlag)
        {
            dwReturn = (DWORD)NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        for (;;)
        {
            // check if there's plenty of room in the buffer
            if ((pMD4Hash->BufLen + dwDataLen) < MD4BLOCKSIZE)
            {
                // just append to whatever's already
                memcpy(pMD4Hash->Buf + pMD4Hash->BufLen, pbData, dwDataLen);

                // set of the trailing buffer length field
                pMD4Hash->BufLen += (BYTE)dwDataLen;
                break;
            }

            // determine what we need to fill the buffer, then do it.
            BytePos = MD4BLOCKSIZE - pMD4Hash->BufLen;
            memcpy(pMD4Hash->Buf + pMD4Hash->BufLen, pbData, BytePos);

            // The buffer is now full, process it.
            nSts = MDupdate(&pMD4Hash->MD, pMD4Hash->Buf,
                            MD4BYTESTOBITS(MD4BLOCKSIZE));
            if (MD4_SUCCESS != nSts)
            {
                dwReturn = (DWORD)NTE_FAIL;
                goto ErrorExit;
            }

            // now it's empty.
            pMD4Hash->BufLen = 0;

            // we processed some bytes, so reflect that and try again
            dwDataLen -= BytePos;
            if (dwDataLen == 0)
                break;
        }
        break;
    }
#endif

#ifdef CSP_USE_MD5
    case CALG_MD5:
    {
        MD5_object *pMD5Hash;

        pMD5Hash = (MD5_object *)pTmpHash->pHashData;

        // make sure the hash is updatable
        if (pMD5Hash->FinishFlag)
        {
            dwReturn = (DWORD)NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        MD5Update(pMD5Hash, pbData, dwDataLen);
        break;
    }
#endif

#ifdef CSP_USE_SHA
    case CALG_SHA:
    {
        A_SHA_CTX *pSHAHash;

        pSHAHash = (A_SHA_CTX *)pTmpHash->pHashData;

        // make sure the hash is updatable
        if (pSHAHash->FinishFlag)
        {
            dwReturn = (DWORD)NTE_BAD_HASH_STATE;
            goto ErrorExit;
        }

        A_SHAUpdate(pSHAHash, (BYTE *)pbData, dwDataLen);
        break;
    }
#endif

#ifdef CSP_USE_MAC
    case CALG_MAC:
#endif // CSP_USE_MAC
    case CALG_HMAC:
        dwSts = LocalMACData(hUID, pTmpHash, pbData, dwDataLen);
        if (ERROR_SUCCESS != dwSts)
        {
            dwReturn = dwSts;
            goto ErrorExit;
        }
        break;

    default:
        dwReturn = (DWORD)NTE_BAD_ALGID;
        goto ErrorExit;
    }

    pTmpHash->dwHashState |= DATA_IN_HASH;

    dwReturn = ERROR_SUCCESS;

ErrorExit:
    fRet = (ERROR_SUCCESS == dwReturn);
    if (pbData)
        _nt_free(pbData, dwDataLen);
    if (!fRet)
        SetLastError(dwReturn);
    return fRet;
}


/*static*/ void
FreeHash(
    IN PNTAGHashList pHash)
{
    if (pHash)
    {
        if (pHash->pHashData)
            _nt_free(pHash->pHashData, pHash->dwDataLen);
        if (pHash->pbHMACInner)
            _nt_free(pHash->pbHMACInner, pHash->cbHMACInner);
        if (pHash->pbHMACOuter)
            _nt_free(pHash->pbHMACOuter, pHash->cbHMACOuter);
        if (pHash->fTempKey)
            CPDestroyKey(pHash->hUID, pHash->hKey);
        _nt_free(pHash, sizeof(NTAGHashList));
    }
}


/*
-   CPDestroyHash
-
*   Purpose:
*                Destory the hash object
*
*
*   Parameters:
*               IN  hUID      -  Handle to the user identifcation
*               IN  hHash     -  Handle to hash object
*
*   Returns:
*/

BOOL WINAPI
CPDestroyHash(
    IN HCRYPTPROV hUID,
    IN HCRYPTHASH hHash)
{
    DWORD           dwReturn = ERROR_INTERNAL_ERROR;
    PNTAGHashList   pTmpHash;
    BOOL            fRet;
    DWORD           dwSts;

    EntryPoint
    // check the user identification
    if (NULL == NTLCheckList(hUID, USER_HANDLE))
    {
        dwReturn = (DWORD)NTE_BAD_UID;
        goto ErrorExit;
    }

    dwSts = NTLValidate(hHash, hUID, HASH_HANDLE, &pTmpHash);
    if (ERROR_SUCCESS != dwSts)
    {
        // NTLValidate doesn't know what error to set
        // so it set NTE_FAIL -- fix it up.
        dwReturn = (dwSts == NTE_FAIL) ? (DWORD)NTE_BAD_HASH : dwSts;
        goto ErrorExit;
    }

    switch (pTmpHash->Algid)
    {
#ifdef CSP_USE_MD2
    case CALG_MD2:
#endif
#ifdef CSP_USE_MD4
    case CALG_MD4:
#endif
#ifdef CSP_USE_MD5
    case CALG_MD5:
#endif
#ifdef CSP_USE_SHA
    case CALG_SHA:
#endif
#ifdef CSP_USE_SSL3SHAMD5
    case CALG_SSL3_SHAMD5:
#endif
#ifdef CSP_USE_MAC
    case CALG_MAC:
    case CALG_HMAC:
#endif
#ifdef CSP_USE_SSL3
    case CALG_SCHANNEL_MASTER_HASH:
    case CALG_TLS1PRF:
#endif
        if (CALG_SCHANNEL_MASTER_HASH == pTmpHash->Algid)
        {
            FreeSChHash((PSCH_HASH)pTmpHash->pHashData);
        }
        memnuke(pTmpHash->pHashData, pTmpHash->dwDataLen);
        break;

    default:
        dwReturn = (DWORD)NTE_BAD_ALGID;
        goto ErrorExit;
    }

    // Remove from internal list first so others can't get to it, then free.
    NTLDelete(hHash);
    FreeHash(pTmpHash);

    dwReturn = ERROR_SUCCESS;

ErrorExit:
    fRet = (ERROR_SUCCESS == dwReturn);
    if (!fRet)
        SetLastError(dwReturn);
    return fRet;
}

/*static*/ DWORD
CopyHash(
    IN PNTAGHashList pOldHash,
    OUT PNTAGHashList *ppNewHash)
{
    DWORD           dwReturn = ERROR_INTERNAL_ERROR;
    PNTAGHashList   pNewHash;
    BOOL            fSts;

    pNewHash = (PNTAGHashList)_nt_malloc(sizeof(NTAGHashList));
    if (NULL == pNewHash)
    {
        dwReturn = ERROR_NOT_ENOUGH_MEMORY;
        goto ErrorExit;
    }

    memcpy(pNewHash, pOldHash, sizeof(NTAGHashList));
    pNewHash->fTempKey = FALSE;
    pNewHash->hKey = 0;
    pNewHash->dwDataLen = 0;
    pNewHash->pHashData = NULL;
    pNewHash->cbHMACInner = 0;
    pNewHash->pbHMACInner = NULL;
    pNewHash->cbHMACOuter = 0;
    pNewHash->pbHMACOuter = NULL;


    //
    // Duplicate the associated key.
    //

    if (0 != pOldHash->hKey)
    {
        fSts = CPDuplicateKey(pNewHash->hUID, pOldHash->hKey, NULL, 0,
                              &pNewHash->hKey);
        if (!fSts)
        {
            dwReturn = GetLastError();
            goto ErrorExit;
        }

        pNewHash->fTempKey = TRUE;
    }


    //
    // Duplicate the hash data.
    //

    if (0 < pOldHash->dwDataLen)
    {
        pNewHash->pHashData = (BYTE*)_nt_malloc(pOldHash->dwDataLen);
        if (NULL == pNewHash->pHashData)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        pNewHash->dwDataLen = pOldHash->dwDataLen;
        memcpy(pNewHash->pHashData, pOldHash->pHashData, pOldHash->dwDataLen);
    }


    //
    // Duplicate HMAC Inner.
    //

    if (0 < pOldHash->cbHMACInner)
    {
        pNewHash->pbHMACInner = (LPBYTE)_nt_malloc(pOldHash->cbHMACInner);
        if (NULL == pNewHash->pbHMACInner)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        pNewHash->cbHMACInner = pOldHash->cbHMACInner;
        memcpy(pNewHash->pbHMACInner, pOldHash->pbHMACInner, pOldHash->cbHMACInner);
    }


    //
    // Duplicate HMAC Outer.
    //

    if (0 < pOldHash->cbHMACOuter)
    {
        pNewHash->pbHMACOuter = (LPBYTE)_nt_malloc(pOldHash->cbHMACOuter);
        if (NULL == pNewHash->pbHMACOuter)
        {
            dwReturn = ERROR_NOT_ENOUGH_MEMORY;
            goto ErrorExit;
        }

        pNewHash->cbHMACOuter = pOldHash->cbHMACOuter;
        memcpy(pNewHash->pbHMACOuter, pOldHash->pbHMACOuter, pOldHash->cbHMACOuter);
    }


    //
    // Return to the caller.
    //

    *ppNewHash = pNewHash;
    return ERROR_SUCCESS;

ErrorExit:
    FreeHash(pNewHash);
    return dwReturn;
}


/*
 -  CPDuplicateHash
 -
 *  Purpose:
 *                Duplicates the state of a hash and returns a handle to it
 *
 *  Parameters:
 *               IN      hUID           -  Handle to a CSP
 *               IN      hHash          -  Handle to a hash
 *               IN      pdwReserved    -  Reserved
 *               IN      dwFlags        -  Flags
 *               IN      phHash         -  Handle to the new hash
 *
 *  Returns:
 */

BOOL WINAPI
CPDuplicateHash(
    IN HCRYPTPROV hUID,
    IN HCRYPTHASH hHash,
    IN DWORD *pdwReserved,
    IN DWORD dwFlags,
    IN HCRYPTHASH *phHash)
{
    DWORD           dwReturn = ERROR_INTERNAL_ERROR;
    PNTAGHashList   pTmpHash;
    PNTAGHashList   pNewHash = NULL;
    BOOL            fRet;
    DWORD           dwSts;

    EntryPoint
    if (NULL != pdwReserved)
    {
        dwReturn = ERROR_INVALID_PARAMETER;
        goto ErrorExit;
    }

    if (0 != dwFlags)
    {
        dwReturn = (DWORD)NTE_BAD_FLAGS;
        goto ErrorExit;
    }

    dwSts = NTLValidate((HNTAG)hHash, hUID, HASH_HANDLE, &pTmpHash);
    if (ERROR_SUCCESS != dwSts)
    {
        dwReturn = (NTE_FAIL == dwSts) ? (DWORD)NTE_BAD_HASH : dwSts;
        goto ErrorExit;
    }

    dwSts = CopyHash(pTmpHash, &pNewHash);
    if (ERROR_SUCCESS != dwSts)
    {
        dwReturn = dwSts;
        goto ErrorExit;
    }

    dwSts = NTLMakeItem(phHash, HASH_HANDLE, (void *)pNewHash);
    if (ERROR_SUCCESS != dwSts)
    {
        dwReturn = dwSts;
        goto ErrorExit;
    }

    dwReturn = ERROR_SUCCESS;

ErrorExit:
    fRet = (ERROR_SUCCESS == dwReturn);
    if (!fRet)
    {
        FreeHash(pNewHash);
        SetLastError(dwReturn);
    }
    return fRet;
}