/**@@@+++@@@@******************************************************************
**
** Microsoft Windows Media
** Copyright (C) Microsoft Corporation. All rights reserved.
**
***@@@---@@@@******************************************************************
*/

/*
 * drmrsaex.c
 *
 * This file implements RSA signing and encryption of variable length data.
 * It assumes the existence of routines to encrypt a single key sized buffer
 *
 */

#include <drmcommon.h>
#include <drmtypes.h>
#include <oemimpl.h>
#include <drmrsaex.h>
#include <drmsha1.h>
#include <drmutilities.h>
#include <byteorder.h>

#ifndef RSA_REVERSE_OS2IP
#define RSA_REVERSE_OS2IP 0
#endif

/*
 * The _GenerateMGF1Mask function generates an MGF1 mask using SHA1 as the hash 
 * (see PKCS#1 v2.1)
 *
 * Parameters:
 *
 *      pbSeed - Specifies the seed to use for the mask
 *
 *      cbSeed - Specifies the size of the seed
 *
 *      cbMask - Specifies the size of the mask that is required
 *
 *      f_ppbData - Returns a buffer containing the generated mask
 *               This buffer must be freed using OEM_free()
 *
 * Return Value:
 *
 *  DRM_SUCCESS - Operation completed successfully
 *  DRM_E_OUTOFMEMORY - There is no enough memory to complete the operation
 *  DRM_E_INVALIDARG - One of the passed in arguments is bad
 */

static DRM_RESULT _GenerateMGF1Mask( 
    IN const DRM_BYTE  *f_pbSeed,
    IN       DRM_DWORD  f_cbSeed,
    IN       DRM_DWORD  f_cbMask,
       OUT   DRM_BYTE **f_ppbMask)
{
    DRM_RESULT  dr = DRM_SUCCESS;
    DRM_DWORD   cIterations  = 0;
    DRM_DWORD   cbHashInput  = 0;
    DRM_DWORD   cbTotalMask  = 0;
    DRM_DWORD   i            = 0;
    DRM_BYTE   *pbHashInput  = NULL;
    DRM_BYTE   *pbMask       = NULL;
    DRM_BYTE   *pbHashOut    = NULL;
    SHA_CONTEXT contextSHA   = { 0 };
        
    cbHashInput = f_cbSeed + SIZEOF(DRM_DWORD);

    ChkArg(cbHashInput >= f_cbSeed
        && f_cbMask    >  0
        && f_pbSeed    != NULL 
        && f_cbSeed    >  0);

    cIterations = f_cbMask / SHA_DIGEST_LEN;

    if (f_cbMask % SHA_DIGEST_LEN > 0)
    {
        cIterations++;
    }

    cbTotalMask = cIterations * SHA_DIGEST_LEN;

    ChkMem(pbMask      = OEM_malloc(cbTotalMask));
    ChkMem(pbHashInput = OEM_malloc(cbHashInput));

    MEMCPY(pbHashInput, f_pbSeed, f_cbSeed);

    pbHashOut = pbMask;
        
    for (i = 0; i < cIterations; i++)
    {
        DRM_SHA_Init(&contextSHA);

        DWORD_TO_NETWORKBYTES( pbHashInput, f_cbSeed, i );

        DRM_SHA_Update  (pbHashInput, cbHashInput, &contextSHA);
        DRM_SHA_Finalize(&contextSHA, pbHashOut);

        pbHashOut += __CB_DECL(SHA_DIGEST_LEN);
    }

    /*
    ** Wipe off the mask bits that we don't need
    */
    DRM_BYT_SetBytes(pbMask, f_cbMask, cbTotalMask - f_cbMask, 0);

    *f_ppbMask = pbMask;
    pbMask = NULL;

    dr = DRM_SUCCESS;

ErrorExit:

    SAFE_OEM_FREE(pbMask);
    SAFE_OEM_FREE(pbHashInput);

    return dr;
}


/*
 * The DrmOAEPDecode function performs the OAEP decode on an encoded buffer
 * (see PKCS#1 v2.1)
 *
 * Parameters:
 *
 *      cbModulus - Specifies the size of the public key modulus
 *
 *      pbEncData - Specifies the input, encoded data of size cbModulus
 *
 *      ppbDecData - Returns a buffer containing processed, OAEP decoded data
 *                   This buffer must be freed using OEM_free()
 *
 *      pcbDecData - Returns the size (in bytes) of the processed, OAEP 
 *                   decoded data
 *
 * Return Value:
 *
 *  DRM_SUCCESS - Operation completed successfully
 *  DRM_E_OUTOFMEMORY - There is no enough memory to complete the operation
 *  DRM_E_INVALIDARG - The specified modulus size is too small
 *  Other failure - Failed to OAEP decode the data.
 */

static DRM_RESULT DrmOAEPDecode(
    IN       DRM_DWORD  cbModulus,
    IN       DRM_BYTE  *pbEncData,
       OUT   DRM_BYTE **ppbDecData,
       OUT   DRM_DWORD *pcbDecData )
{
    DRM_RESULT dr         = DRM_SUCCESS;
    DRM_BYTE *pbDataBlock = NULL;
    DRM_DWORD cbDataBlock = 0;
    DRM_BYTE *pbSeedMask  = NULL;
    DRM_BYTE *pbDBMask    = NULL;
    DRM_BYTE rgbLHash[ __CB_DECL( SHA_DIGEST_LEN ) ];
    DRM_BYTE cSeed[__CB_DECL(SHA_DIGEST_LEN)];
    DRM_DWORD dwDataBlock = 0;
    DRM_DWORD dwCur = 0;
    DRM_DWORD dwSeed = 0;
    SHA_CONTEXT contextSHA;
        
    ChkArg( cbModulus >= ( 2 * SHA_DIGEST_LEN + 2 ) );

    /*
     * Set up pointer to seed/maskedseed
     */
    dwSeed = 1;
    DRM_BYT_CopyBytes(cSeed, 0, pbEncData, dwSeed, SHA_DIGEST_LEN);

    /*
     * Set up pointer to DB/maskedDB
     */
    dwDataBlock = 1 + SHA_DIGEST_LEN;
    cbDataBlock = cbModulus 
                - 1 
                - SHA_DIGEST_LEN;

    ChkMem(pbDataBlock = OEM_malloc(cbDataBlock));
    DRM_BYT_CopyBytes(pbDataBlock, 0, pbEncData, dwDataBlock, cbDataBlock);

    /*
     * First byte of the EM must be 0
     */
    if( GET_BYTE( pbEncData, 0 ) != 0x00 )
    {
        ChkDR( DRM_E_FAIL );
    }
    
    /*
     * Compute the seed mask and decode the seed
     */
    ChkDR(_GenerateMGF1Mask(pbDataBlock, 
                            cbDataBlock, 
                            SHA_DIGEST_LEN,
                           &pbSeedMask));

    DRM_XOR( cSeed, pbSeedMask, SHA_DIGEST_LEN );

    /*
     * Compute DB Mask from the seed and decode DB
     */
    ChkDR(_GenerateMGF1Mask(cSeed,
                            SHA_DIGEST_LEN,
                            cbDataBlock,
                           &pbDBMask));

    DRM_XOR( pbDataBlock, pbDBMask, cbDataBlock );

    /*
     * Compute LHash for empty label and verify that DB starts with LHash
     */

    DRM_SHA_Init( &contextSHA );
    DRM_SHA_Update( NULL, 0, &contextSHA );
    DRM_SHA_Finalize( &contextSHA, rgbLHash );

    if( MEMCMP( rgbLHash, pbDataBlock, SHA_DIGEST_LEN ) != 0 )
    {
      ChkDR( DRM_E_FAIL );
    }
    
    /*
     * Locate a 0x01 value byte to indicate start of original message
     */
     
    dwCur = SHA_DIGEST_LEN;
    while( dwCur < (dwDataBlock + cbDataBlock) 
	   && GET_BYTE(pbDataBlock, dwCur) == 0x00 ) 
    {
        dwCur++;
    }

    if(  dwCur == dwDataBlock + cbDataBlock 
	 || GET_BYTE(pbDataBlock, dwCur) != 0x01 ) 
    {
        ChkDR( DRM_E_FAIL );
    }

    /*
     * Finally, output the decoded message
     */

    dwCur++;     
    *pcbDecData = cbModulus - dwCur - dwDataBlock;
    if( *pcbDecData > 0 ) 
    {
        ChkMem( *ppbDecData = OEM_malloc( *pcbDecData ) );
        DRM_BYT_CopyBytes( *ppbDecData, 0, pbDataBlock, dwCur, *pcbDecData );
    }
    else 
    {
        *ppbDecData = NULL;
    }

    DRM_BYT_CopyBytes( pbEncData, dwSeed, cSeed, 0, SHA_DIGEST_LEN );
    DRM_BYT_CopyBytes( pbEncData, dwDataBlock, pbDataBlock, 0, cbDataBlock );

ErrorExit:
    if( pbSeedMask != NULL ) 
    {
        OEM_free( pbSeedMask );
    }
    if( pbDBMask != NULL ) 
    {
        OEM_free( pbDBMask );
    }
    if (pbDataBlock != NULL )
    {
        OEM_free( pbDataBlock );
    }

    return dr;

}

static DRM_RESULT DrmOAEPEncode(
    IN       DRM_DWORD  cbModulus,
    IN const DRM_BYTE  *pbMsg,
    IN const DRM_DWORD  cbMsg,
       OUT   DRM_BYTE  *pbEncodedMsg )
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD cbMaxMsg     = 0;
    DRM_DWORD cbEncodedMsg = 0;
    DRM_DWORD cbDataBlock  = 0;
    DRM_BYTE *pbDataBlock  = NULL;
    DRM_BYTE *pbDBMask     = NULL;
    DRM_BYTE *pbSeedMask   = NULL;
    SHA_CONTEXT contextSHA;
    DRM_BYTE cSeed[__CB_DECL(SHA_DIGEST_LEN)];
    DRM_BYTE cHash[__CB_DECL(SHA_DIGEST_LEN)];
    DRM_DWORD dwOffset;
    DRM_DWORD dwSeedOffset;
    DRM_DWORD dwDataBlockOffset;
   
    ChkArg( cbModulus >= ( 2 * SHA_DIGEST_LEN + 2 ) );
    ChkArg( pbMsg        != NULL
         && pbEncodedMsg != NULL );

    cbEncodedMsg = cbModulus;
    cbDataBlock  = cbEncodedMsg 
                 - SHA_DIGEST_LEN 
                 - 1;
    cbMaxMsg     = cbDataBlock 
                 - SHA_DIGEST_LEN 
                 - 1;


    ChkArg( cbMsg <= cbMaxMsg );
    
    PUT_BYTE(pbEncodedMsg, 0, 0x00);                 /* Set first byte of EM to 0 */    
    dwSeedOffset = 1;                                /* Set up pointer to the seed/maskedSeed */
    dwOffset = dwDataBlockOffset = dwSeedOffset + SHA_DIGEST_LEN; /* Set up pointer to DB/maskedDB */
       
    /* Compute Hash of the empty Label */
    DRM_SHA_Init( &contextSHA );
    DRM_SHA_Update( NULL, 0, &contextSHA );
    DRM_SHA_Finalize( &contextSHA, cHash );
    DRM_BYT_CopyBytes(pbEncodedMsg, dwOffset, cHash, 0, SHA_DIGEST_LEN);
    dwOffset += SHA_DIGEST_LEN;

    /* Construct PS */
    DRM_BYT_SetBytes(pbEncodedMsg, dwOffset, cbMaxMsg - cbMsg, 0);
    dwOffset += cbMaxMsg - cbMsg;
    
    /* Write the 0x01 value byte to mark start of original message */
    PUT_BYTE(pbEncodedMsg, dwOffset, 0x01);
    dwOffset++;

    /* Copy the original message */
    DRM_BYT_CopyBytes(pbEncodedMsg, dwOffset, pbMsg, 0, cbMsg);
    dwOffset += cbMsg;
    
    /* Generate the seed in DB */
    ChkDR( OEM_GenRandomBytes( cSeed, SHA_DIGEST_LEN ) );
    
    /* Compute the DB Mask from the seed and XOR it with DB */
    ChkDR(_GenerateMGF1Mask(cSeed, 
                            SHA_DIGEST_LEN, 
                            cbDataBlock, 
                            &pbDBMask));

    ChkMem( pbDataBlock = OEM_malloc(cbDataBlock) );
    DRM_BYT_CopyBytes( pbDataBlock, 0, pbEncodedMsg, dwDataBlockOffset, cbDataBlock );
    DRM_XOR( pbDataBlock, pbDBMask, cbDataBlock );
    
    /* Next, compute the seed mask and XOR it with the seed */
    ChkDR(_GenerateMGF1Mask(pbDataBlock, 
                            cbDataBlock, 
                            SHA_DIGEST_LEN, 
                           &pbSeedMask));

    DRM_XOR( cSeed, pbSeedMask, SHA_DIGEST_LEN );
    DRM_BYT_CopyBytes(pbEncodedMsg, dwSeedOffset, cSeed, 0, SHA_DIGEST_LEN);
    DRM_BYT_CopyBytes(pbEncodedMsg, dwDataBlockOffset, pbDataBlock, 0, cbDataBlock);
    
    
ErrorExit:
    OEM_free( pbDBMask );
    OEM_free( pbSeedMask );
    OEM_free( pbDataBlock );
    return dr;
}
    
/*
 * The _DrmRsaProcess function provides common processing for Sign/Verify and Encrypt/Decrypt
 *
 * Parameters:
 *
 *      pKey - Specifies the key to use
 *          NULL indicates that the private key baked into the device should be used
 *
 *      f_pbData - Specifies the data input
 *      f_cbData - Specifies the size (in bytes) of the input data
 *
 *      f_ppbData - Returns a buffer containing processed data
 *          This buffer must be freed using OEM_free()
 *
 *      f_pcbData - Returns the size (in bytes) of the processed data
 *
 * Return Value:
 *
 *  DRM_SUCCESS - Operation completed successfully
 *  DRM_E_OUTOFMEMORY - There is no enough memory to complete the operation
 *  DRM_E_INVALIDARG - One of the arguments is invalid
 */

static DRM_RESULT _DrmRsaProcess(
    IN const DRM_VOID  *f_pvKey,
    IN const DRM_BYTE  *f_pbData,
    IN       DRM_DWORD  f_cbData,
       OUT   DRM_BYTE **f_ppbData,
       OUT   DRM_DWORD *f_pcbData)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_BYTE *pbDataOut = NULL;
    DRM_DWORD cbDataOut = 0;
    DRM_BYTE *pbDataInForProcess = NULL;
    enum DRM_RSA_KEY_TYPE kt = DRM_RSA_KEY_TYPE_UNKNOWN;

    /*
     * Determine the size of the working buffer the underlying routines need
     */
    ChkArg(f_ppbData != NULL
        && f_pvKey   != NULL
        && f_pcbData != NULL);

    ChkArg(f_pbData != NULL 
        && f_cbData  > 0);

    
    cbDataOut = OEM_DrmRsaKeySize    (f_pvKey);
    
    kt        = OEM_DrmRsaIdentifyKey(f_pvKey);
    
	
    ChkArg(cbDataOut > 0
        && kt       != DRM_RSA_KEY_TYPE_UNKNOWN);

    /*
    ** Allocate working buffers
    */

    ChkMem( pbDataOut = OEM_malloc( cbDataOut * 2 ) );

    MEMSET( pbDataOut, 0, cbDataOut * 2 );

    pbDataInForProcess = pbDataOut + __CB_DECL(cbDataOut);

    /*
     * Copy the input data into the larger working buffer
     */

    DRMASSERT(f_cbData <= cbDataOut);
    ChkArg   (f_cbData <= cbDataOut);

    MEMCPY( pbDataInForProcess, f_pbData, f_cbData );
    /*
     * Call the underlying routines
     */
    
    if (kt == DRM_RSA_KEY_TYPE_PUBLIC)
    {
        ChkFAIL( OEM_DrmRsaEncPublic( (DRM_RSA_PUBLIC_KEY *)f_pvKey,
                                       pbDataInForProcess,
                                       pbDataOut ) );
    }
    else
    {
        ChkFAIL( OEM_DrmRsaDecPrivate( (DRM_RSA_PRIVATE_KEY *)f_pvKey,
                                        pbDataInForProcess,
                                        pbDataOut ) );
    }

    /*
     * Success
     */
    *f_ppbData = pbDataOut;
    *f_pcbData = cbDataOut;
    pbDataOut   = NULL;

ErrorExit:

    SAFE_OEM_FREE(pbDataOut);

    return dr;
}

#define CB_ZERO_PADDING 8

static DRM_RESULT _PSSVerify(
    IN const DRM_BYTE      *f_pbData,
    IN const DRM_SUBSTRING *f_pdasstrData,
    IN       DRM_BYTE      *f_pbEncoding,
    IN       DRM_DWORD      f_cbEMBits)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD cbEM         = 0;
    DRM_DWORD cbDB         = 0;
    DRM_DWORD cbMPrime     = 0;
    DRM_DWORD cbZeroBits   = 0;
    DRM_DWORD i            = 0;
#if SIXTEEN_BIT_ADDRESSING
    DRM_DWORD ichData      = 0;
    DRM_DWORD cchData      = 0;
    DRM_BYTE pbTemp[__CB_DECL(SHA_DIGEST_LEN)];
    DRM_BYTE *pbTemp2 = NULL;
#else
    DRM_BYTE *pbMPrimeHash = NULL;
#endif /* SIXTEEN_BIT_ADDRESSING */
    DRM_BYTE *pbDB         = NULL;
    DRM_BYTE *pbMPrime     = NULL;
    DRM_BYTE *pbSalt       = NULL;
    DRM_BYTE *pbCurr       = NULL;
    DRM_BYTE *pbDBMask     = NULL;
    DRM_BYTE  bMask        = 0x00;
    DRM_BYTE  rgbMPrimeHash [__CB_DECL(SHA_DIGEST_LEN)];
    SHA_CONTEXT contextSHA   = { 0 };
    const DRM_DWORD cbSalt = 0;

    cbEM = (f_cbEMBits % BITS_PER_STANDARD_BYTE == 0)
         ? (f_cbEMBits / BITS_PER_STANDARD_BYTE)
         : (f_cbEMBits / BITS_PER_STANDARD_BYTE) + 1;

    cbZeroBits = (BITS_PER_STANDARD_BYTE * cbEM) - f_cbEMBits;

    ChkArg(cbEM >= SHA_DIGEST_LEN + cbSalt + 2);

    /*
    ** Verify last bit of EM
    */

    ChkArg(GET_BYTE(f_pbEncoding, cbEM - 1) == 0xBC);

    /*
    ** Generate partial M'. We don't yet have the salt so leave it empty
    */

    cbMPrime = BITS_PER_STANDARD_BYTE + SHA_DIGEST_LEN + cbSalt;
    ChkMem(pbMPrime = OEM_malloc(cbMPrime));

    pbCurr = pbMPrime;
    ZEROMEM(pbCurr, CB_ZERO_PADDING);     /* 8-bytes of zero padding*/
    pbCurr += __CB_DECL(CB_ZERO_PADDING); /* OK; always even */

    DRM_SHA_Init  (&contextSHA);                  /* Original message hash */
    DRM_SHA_UpdateOffset(f_pbData, f_pdasstrData->m_ich, f_pdasstrData->m_cch, &contextSHA);
    DRM_SHA_Finalize (&contextSHA, pbCurr);

    pbCurr += __CB_DECL(SHA_DIGEST_LEN);

    if (cbSalt > 0)            /* Remember pointer to Salt: fill this later */
    {
        pbSalt = pbCurr;
    }

    /*
    ** Set up DB and Hash(M') in EM
    */

    cbDB = cbEM - SHA_DIGEST_LEN - 1;
    pbDB = f_pbEncoding;

#if SIXTEEN_BIT_ADDRESSING
    DRM_BYT_CopyBytes(pbTemp, 0, pbDB, cbDB, SHA_DIGEST_LEN);
#else
    pbMPrimeHash = pbDB + __CB_DECL(cbDB);
#endif
    
    /*
    ** Check that the requisite leftmost bits of DB are 0
    */

    bMask = (DRM_BYTE)( 0xFF << (BITS_PER_STANDARD_BYTE - cbZeroBits) );
    
    ChkArg((GET_BYTE(pbDB, 0) & bMask) == 0);

    /*
    ** Compute DBMask and extract DB
    */
#if SIXTEEN_BIT_ADDRESSING
    ChkDR(_GenerateMGF1Mask(pbTemp,
                            SHA_DIGEST_LEN, 
                            cbDB, 
                           &pbDBMask));
#else
    ChkDR(_GenerateMGF1Mask(pbMPrimeHash, 
                            SHA_DIGEST_LEN, 
                            cbDB, 
                           &pbDBMask));
#endif
    DRM_XOR(pbDB, pbDBMask, cbDB);

    PUT_BYTE( pbDB, 0, (DRM_BYTE)(GET_BYTE(pbDB, 0) & (0xFF >> cbZeroBits)));

    /*
    ** Verify that bytes before the salt are all zero followed by 0x01
    */
    
    for (i = 0; i < cbDB - cbSalt - 1; i++)
    {
        if (GET_BYTE(pbDB, i) != 0)
        {
            ChkDR(DRM_E_FAIL);
        }
    }

    if (GET_BYTE(pbDB, cbDB - cbSalt - 1) != 0x01)
    {
        ChkDR(DRM_E_FAIL);
    }

    /*
    ** Fill the salt in M' now, compute Hash(M') and check against the hash in EM
    */
    
    DRM_BYT_CopyBytes(pbSalt,
                      0, 
                      pbDB, 
                      cbDB - cbSalt, 
                      cbSalt);

    DRM_SHA_Init  (&contextSHA);
    DRM_SHA_Update(pbMPrime, cbMPrime, &contextSHA);
    DRM_SHA_Finalize (&contextSHA, rgbMPrimeHash);

    if (DRM_BYT_CompareBytes(pbDB, cbDB, rgbMPrimeHash, 0, SHA_DIGEST_LEN) != 0)
    {
        ChkDR(DRM_E_FAIL);
    }

ErrorExit:

    SAFE_OEM_FREE(pbMPrime);
    SAFE_OEM_FREE(pbDBMask);

    return dr;
}

static DRM_RESULT _PSSEncode(
    IN const DRM_BYTE      *f_pbData,
    IN const DRM_SUBSTRING *f_pdasstrData,
    IN DRM_DWORD            f_cbEMBits,
    OUT DRM_BYTE           *f_pbEncodedMsg,
    IN const DRM_DWORD      f_cbEncodedMsg)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD cbEM         = 0;
#if SIXTEEN_BIT_ADDRESSING
    DRM_DWORD ichData      = 0;
    DRM_DWORD cchData      = 0;
#endif
    DRM_BYTE *pbDB         = NULL;
    DRM_DWORD cbDB         = 0;
    DRM_BYTE *pbMHash      = NULL;
    DRM_BYTE *pbMPrime     = NULL;
    DRM_DWORD cbMPrime     = 0;
    DRM_DWORD cbZeroBits   = 0;
    DRM_BYTE *pbMPrimeHash = NULL;
    DRM_BYTE *pbCurr       = NULL;
    DRM_BYTE *pbDBMask     = NULL;
    SHA_CONTEXT contextSHA = { 0 };

    cbEM = (f_cbEMBits % BITS_PER_STANDARD_BYTE == 0)
         ? (f_cbEMBits / BITS_PER_STANDARD_BYTE)
         : (f_cbEMBits / BITS_PER_STANDARD_BYTE) + 1;

    cbZeroBits = (BITS_PER_STANDARD_BYTE * cbEM) - f_cbEMBits;

    ChkArg(cbEM >= SHA_DIGEST_LEN + 2 
        && cbEM == f_cbEncodedMsg);

    pbDB = f_pbEncodedMsg;
    cbDB = cbEM - SHA_DIGEST_LEN - 1;  
    pbMPrimeHash = pbDB + cbDB;

    /*
    ** Generate M' and compute its hash
    */

    cbMPrime = CB_ZERO_PADDING + SHA_DIGEST_LEN;
    ChkMem(pbMPrime = OEM_malloc(cbMPrime));

    pbCurr = pbMPrime;
    ZEROMEM(pbCurr, CB_ZERO_PADDING);
    pbCurr += __CB_DECL(CB_ZERO_PADDING); /* 16-bit safe: CB_ZERO_PADDING always even */

    DRM_SHA_Init  (&contextSHA);

#if SIXTEEN_BIT_ADDRESSING
    cchData = f_pdasstrData->m_cch;
    ichData = f_pdasstrData->m_ich;

    if (ISODD(ichData))
    {
        DRM_BYTE b = GET_BYTE(f_pbData, ichData);

        DRM_SHA_Update(&b, 1, &contextSHA);
        ichData++;
        cchData--;
    }
    DRM_SHA_Update(((DRM_BYTE *) f_pbData) + __CB_DECL(ichData), cchData, &contextSHA);
#else
    DRM_SHA_Update(((DRM_BYTE *) f_pbData) + f_pdasstrData->m_ich, f_pdasstrData->m_cch, &contextSHA);
#endif

    DRM_SHA_Finalize (&contextSHA, pbCurr);
    pbMHash = pbCurr;
    pbCurr += __CB_DECL(SHA_DIGEST_LEN); /* 16-bit safe: SHA_DIGEST_LEN always even */

    /*
    ** Calculate Hash(M')
    */

    DRM_SHA_Init  (&contextSHA);
    DRM_SHA_Update(pbMPrime, cbMPrime, &contextSHA);
    DRM_SHA_Finalize (&contextSHA, pbMPrimeHash);

    pbCurr = pbDB;
    ZEROMEM(pbCurr, cbDB - 1);

    PUT_BYTE(pbCurr, cbDB - 1, 0x01); /* 0x01 byte separating padding and salt */

    ChkDR(_GenerateMGF1Mask(pbMPrimeHash, 
                            SHA_DIGEST_LEN, 
                            cbDB, 
                           &pbDBMask));

    DRM_XOR(pbDB, pbDBMask, cbDB);

    pbDB [0] &= (0xFF >> cbZeroBits);

    PUT_BYTE(f_pbEncodedMsg, cbEM - 1, 0xBC);

    /*
    ** Output the digest if requested
    */

ErrorExit:

    SAFE_OEM_FREE(pbMPrime);
    SAFE_OEM_FREE(pbDBMask);

    return dr;
}


/*
 * The DrmRsaOaepDecrypt function decrypts a cipher text that was encrypted 
 * using PKCS #1 RSAES-OAEP v2.1. MGF1 is used as the mask generation function
 * and SHA1 is the hashing algorithm.
 *
 * This version of OAEP decrypt uses a "Label" of an empty string.
 *
 * Parameters:
 *
 *      pPrivateKey - Specifies an RSA private key (as returned from DrmRsaSetPrivateKey)
 *          NULL indicates that the private key baked into the device should be used
 *
 *      pbCiphertext - Specifies the cipher text to be decrypted
 *
 *      cbCiphertext - Specifies the size (in bytes) of the cipher text.
 *          This size must be the modulus length of the passed in RSA private key.
 *          If not, DRM_E_INVALIDARG is returned.
 *
 *      ppbPlaintext - Returns a buffer containing the resulting plain text
 *          The returned buffer must be freed using OEM_free()
 *
 *      pcbPlaintext - Returns the size (in bytes) of the plain text
 *
 * Return Value:
 *  DRM_SUCCESS - Operation completed successfully
 *  DRM_E_OUTOFMEMORY - There is no enough memory to complete the operation
 *  DRM_E_INVALIDARG - The passed in cipher text is the wrong size
 */

DRM_RESULT DRM_API DrmRsaOaepDecrypt(
    IN DRM_RSA_PRIVATE_KEY *pPrivateKey,
    IN DRM_BYTE            *pbCiphertext,
    IN       DRM_DWORD            cbCiphertext,
       OUT   DRM_BYTE           **ppbPlaintext,
       OUT   DRM_DWORD           *pcbPlaintext )
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD cbModulus   = 0;
    DRM_BYTE *pbData      = NULL;
    DRM_BYTE *pbTempBuff  = NULL;
    DRM_BYTE *pbDecrypted = NULL;
    DRM_DWORD cbDecrypted = 0;
#if RSA_REVERSE_OS2IP == 1
    DRM_DWORD i           = 0;
#endif  /* RSA_REVERSE_OS2IP */

    ChkArg( pbCiphertext != NULL 
         && ppbPlaintext != NULL
         && pcbPlaintext != NULL );
    
    /*
     * Ensure the cipher text is the right size
     *
     * This is the correct test for RSA OEAP
     */

    cbModulus = OEM_DrmRsaModulusSize( pPrivateKey );

    ChkArg( cbModulus == cbCiphertext );

    /*
     * Perform OS2IP on encrypted data if required
     */

#if RSA_REVERSE_OS2IP == 1

#if TARGET_LITTLE_ENDIAN

    pbTempBuff = OEM_malloc( cbModulus );
    ChkMem( pbTempBuff );

    for( i = 0; i < cbModulus; i++ )
    {
        PUT_BYTE( pbTempBuff, i, GET_BYTE( pbCiphertext, (cbModulus - 1) - i ) );
    }

    pbData = pbTempBuff;

#else

    pbData = pbCiphertext;

#endif

#else   /* RSA_REVERSE_OS2IP */

    pbData = pbCiphertext;

#endif  /* RSA_REVERSE_OS2IP */

    /*
     * Decrypt the buffer with the private key
     */

    ChkDR(_DrmRsaProcess(pPrivateKey, 
                         pbData, 
                         cbModulus, 
                        &pbDecrypted, 
                        &cbDecrypted));
    
    /*
     * Perform I2OSP on decrypted data if required
     */

#if RSA_REVERSE_OS2IP == 1

#if TARGET_LITTLE_ENDIAN

    for( i = 0; i < cbModulus; i++ )
    {
        PUT_BYTE( pbTempBuff, i, GET_BYTE( pbDecrypted, (cbModulus - 1) - i ) );
    }

    pbData = pbTempBuff;

#else

    pbData = pbDecrypted;

#endif

#else   /* RSA_REVERSE_OS2IP */

    pbData = pbDecrypted;

#endif  /* RSA_REVERSE_OS2IP */

    /*
     * Decode EM and hand out
     */

    ChkDR( DrmOAEPDecode( cbModulus, pbData, ppbPlaintext, pcbPlaintext ) );

ErrorExit:

    if ( pbTempBuff != NULL ) 
    {
        OEM_free( pbTempBuff );
    }
    if ( pbDecrypted != NULL ) 
    {
        OEM_free( pbDecrypted );
    }

    return dr;
}

DRM_RESULT DRM_API DrmRsaOaepEncrypt(
    IN DRM_RSA_PUBLIC_KEY *pPublicKey,
    IN DRM_BYTE           *pbPlaintext,
    IN       DRM_DWORD           cbPlaintext,
       OUT   DRM_BYTE          **ppbCiphertext,
       OUT   DRM_DWORD          *pcbCiphertext )
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD cbModulus = 0;
    DRM_BYTE *pbEncodedData = NULL;
    DRM_BYTE *pbEncryptedData = NULL;
    DRM_DWORD cbEncryptedData = 0;
    DRM_BYTE *pbDataOut = NULL;
    DRM_DWORD i = 0;

    ChkArg( pPublicKey    != NULL 
         && pbPlaintext   != NULL
         && cbPlaintext    > 0 
         && ppbCiphertext != NULL
         && pcbCiphertext != NULL );
    
    /*
     * Ensure the cipher text is the right size
     */

    cbModulus = OEM_DrmRsaModulusSize( pPublicKey );
    ChkArg( cbModulus > 0);
    
    /* Allocate buffer to accomodate EM and OS2IP(EM) */
    ChkMem( pbEncodedData = OEM_malloc( 2 * cbModulus ) );

    /* Compute EM */
    ChkDR( DrmOAEPEncode( cbModulus,
                          pbPlaintext, 
                          cbPlaintext, 
                          pbEncodedData + __CB_DECL(cbModulus)));

#if RSA_REVERSE_OS2IP == 1

    /* Perform OS2IP(EM): Reverse EM */
    for( i = 0; i < cbModulus; i++ )
    {
        PUT_BYTE(pbEncodedData, i,  GET_BYTE(pbEncodedData, 2*cbModulus - 1 - i));
    }

#else   /* RSA_REVERSE_OS2IP */

    for( i = 0; i < cbModulus; i++ )
    {
        PUT_BYTE(pbEncodedData, i, GET_BYTE(pbEncodedData, cbModulus + i));
    }

#endif  /* RSA_REVERSE_OS2IP */

    /* Encrypt using the public key */
    ChkDR( _DrmRsaProcess(pPublicKey, 
                          pbEncodedData, 
                          cbModulus, 
                         &pbEncryptedData, 
                         &cbEncryptedData));

    /*
    ** Compute I2OSP for encrypted message: reverse encrypted
    ** message and hand out
    */
    ChkMem( pbDataOut = OEM_malloc( cbModulus ) );

#if RSA_REVERSE_OS2IP == 1

    for( i = 0; i < cbModulus; i++ )
    {
        pbDataOut[ i ] = pbEncryptedData[ cbModulus - 1 - i ];
    }

#else   /* RSA_REVERSE_OS2IP */

    for( i = 0; i < cbModulus; i++ )
    {
        PUT_BYTE(pbDataOut, i, GET_BYTE(pbEncryptedData, i ) );
    }

#endif  /* RSA_REVERSE_OS2IP */

    *ppbCiphertext = pbDataOut;
     pbDataOut     = NULL;
    *pcbCiphertext = cbModulus;

ErrorExit:
    OEM_free( pbEncodedData );
    OEM_free( pbEncryptedData );
    SAFE_OEM_FREE( pbDataOut );

    return dr;
}

/*****************************************************************************
** Function: DrmRsaSign
**
** Synopsis: sign the given data with the given privkey and return an 
**           allocated signature.  
**
:* Caveat:   caller is responsible for releasing the buffer returned in 
**           f_ppbSignature
**
** Arguments:
**           [f_pkeyPrivate]
**           [f_pbData]
**           [f_pdasstr] 
**           [f_ppbSignature]
**           [f_pcbSignature]
*****************************************************************************/

DRM_RESULT DRM_API DrmRsaSign(
       IN  DRM_RSA_PRIVATE_KEY *f_pkeyPrivate,
       IN  DRM_BYTE            *f_pbData,
       IN  DRM_SUBSTRING       *f_pdasstr, 
       OUT DRM_BYTE           **f_ppbSignature,
       OUT DRM_DWORD           *f_pcbSignature)

{
    DRM_RESULT dr              = DRM_SUCCESS;
    DRM_DWORD  cbModulus       = 0;
    DRM_BYTE  *pbDataEncoded   = NULL;
    DRM_BYTE  *pbDataEncrypted = NULL;
    DRM_DWORD  cbDataEncrypted = 0;
    DRM_BYTE  *pbDataOut       = NULL;
    DRM_DWORD  i               = 0;

    ChkArg(f_pbData        != NULL 
        && f_ppbSignature  != NULL
        && f_pcbSignature  != NULL
        && f_pdasstr       != NULL
        && f_pdasstr->m_cch > 0);

    ChkDR(OEM_DrmRsaParsePrivateKey(f_pkeyPrivate, NULL, NULL, &cbModulus, NULL, NULL));
 
    ChkArg(cbModulus > 0);

    ChkMem(pbDataEncoded = OEM_malloc(2 * cbModulus));

    ZEROMEM(pbDataEncoded, 2 * cbModulus);

    ChkDR(_PSSEncode(f_pbData, 
                     f_pdasstr, 
                    (cbModulus * BITS_PER_STANDARD_BYTE) - 1, 
                     pbDataEncoded + __CB_DECL(cbModulus), 
                     cbModulus));

    for (i = 0; i < cbModulus; i++)
    {
        PUT_BYTE(pbDataEncoded, i, GET_BYTE(pbDataEncoded, ((2 * cbModulus) - 1 - i)));
    }

    ChkDR(_DrmRsaProcess(f_pkeyPrivate,
                         pbDataEncoded,
                         cbModulus,
                        &pbDataEncrypted,
                        &cbDataEncrypted));

    ChkMem(pbDataOut = OEM_malloc(cbModulus));

    for (i = 0; i < cbModulus; i++)
    {
        PUT_BYTE(pbDataOut, i, GET_BYTE(pbDataEncrypted, (cbModulus - 1 - i)));
    }

    *f_ppbSignature = pbDataOut;
    *f_pcbSignature = cbModulus;
    pbDataOut = NULL;
    
ErrorExit:

    SAFE_OEM_FREE(pbDataOut);
    SAFE_OEM_FREE(pbDataEncrypted);
    SAFE_OEM_FREE(pbDataEncoded);

    return dr;
}

DRM_RESULT DRM_API DrmRsaVerify(
    IN const DRM_CHAR           *f_pszBase,
    IN       DRM_SUBSTRING      *f_pdasstrData,
    IN       DRM_RSA_PUBLIC_KEY *f_ppubkey,
    IN const DRM_BYTE           *f_pbSignature,
    IN const DRM_DWORD           f_cbSignature)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD  cbModulus       = 0;
    DRM_BYTE  *pbDataBuff      = NULL;
    DRM_BYTE  *pbDecryptedData = NULL;
    DRM_DWORD  cbDecryptedData = 0;
#if RSA_REVERSE_OS2IP == 1
    DRM_DWORD  i               = 0;
#endif  /* RSA_REVERSE_OS2IP */
    const DRM_BYTE *pbDataIn   = (const DRM_BYTE *) f_pszBase;

    ChkArg(f_pszBase     != NULL 
        && f_pdasstrData != NULL
        && f_pbSignature != NULL
        && f_pdasstrData->m_cch > 0);

    ChkDR(OEM_DrmRsaParsePublicKey(f_ppubkey, NULL, NULL, &cbModulus));

    /*
    ** Signed data must be equal in length to the modulus
    */

	if(!(cbModulus > 0 && cbModulus == f_cbSignature))
    {
        TRACE(("!(cbModulus > 0 && cbModulus == f_cbSignature): cbModulus = %ld; f_cbSignature = %ld\n", cbModulus, f_cbSignature));
    }
    ChkArg(cbModulus  > 0
        && cbModulus == f_cbSignature);

    /*
    ** Perform OS2IP on the signature: reverse the signature
    */
    
#if RSA_REVERSE_OS2IP == 1

    ChkMem(pbDataBuff = OEM_malloc(cbModulus));

    for (i = 0; i < cbModulus; i++)
    {
        PUT_BYTE(pbDataBuff, i, GET_BYTE(f_pbSignature, cbModulus - 1 - i));
    }

#else   /* RSA_REVERSE_OS2IP */

    pbDataBuff = (DRM_BYTE*)f_pbSignature;

#endif  /* RSA_REVERSE_OS2IP */

    /*
    ** Decrypt
    */
    ChkDR(_DrmRsaProcess(f_ppubkey,
                         pbDataBuff,
                         cbModulus,
                        &pbDecryptedData,
                        &cbDecryptedData));

    /* Perform I2OSP on the decrypted data to get EM: reverse 
    ** decrypted Data
    */

#if RSA_REVERSE_OS2IP == 1

    for (i = 0; i < cbModulus; i++)
    {
        PUT_BYTE(pbDataBuff, i, GET_BYTE(pbDecryptedData, cbModulus - 1 - i));
    }

#else   /* RSA_REVERSE_OS2IP */

    pbDataBuff = pbDecryptedData;

#endif  /* RSA_REVERSE_OS2IP */

    /* Verify EM 
    */
    ChkDR(_PSSVerify(pbDataIn, 
                     f_pdasstrData, 
                     pbDataBuff, 
                    (cbModulus * BITS_PER_STANDARD_BYTE) - 1));

ErrorExit:

    SAFE_OEM_FREE(pbDecryptedData);
#if RSA_REVERSE_OS2IP == 1
    SAFE_OEM_FREE(pbDataBuff);
#endif  /* RSA_REVERSE_OS2IP */

    return( dr );
}

