/*************************************************************************
* Copyright (C) 2013 Intel Corporation
*
* Licensed under the Apache License,  Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 	http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law  or agreed  to  in  writing,  software
* distributed under  the License  is  distributed  on  an  "AS IS"  BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the  specific  language  governing  permissions  and
* limitations under the License.
*************************************************************************/

/*
//
//  Purpose:
//     Cryptography Primitive.
//     RSASSA-PSS
//
//     Signatire Scheme with Appendix Signatute Generation
//     (Ppobabilistic Signature Scheme)
//
//  Contents:
//        ippsRSASign_PSS_rmf()
//
*/

#include "owndefs.h"
#include "owncp.h"
#include "pcpngrsa.h"
#include "hash/pcphash_rmf.h"
#include "pcptool.h"

#include "pcprsa_pss_preproc.h"

/*F*
// Name: ippsRSASign_PSS_rmf
//
// Purpose: Performs Signature Generation according to RSASSA-PSS
//
// Returns:                   Reason:
//    ippStsNotSupportedModeErr  invalid hashAlg value
//
//    ippStsNullPtrErr           NULL == pMsg
//                               NULL == pSalt
//                               NULL == pSign
//                               NULL == pPrvKey
//                               NULL == pPubKey
//                               NULL == pMethod
//                               NULL == pBuffer
//
//    ippStsLengthErr            msgLen<0
//                               saltLen<0
//                               emLen < (hashLen +saltLen +2),
//                                  where emLen = (BITSIZE(RSA)-1)/8
//
//    ippStsContextMatchErr      !RSA_PRV_KEY_VALID_ID()
//                               !RSA_PUB_KEY_VALID_ID()
//
//    ippStsIncompleteContextErr private or/and public key is not set up
//
//    ippStsNoErr                no error
//
// Parameters:
//    pMsg        pointer to the message to be signed
//    msgLen      lenfth of the message
//    pSalt       "salt" pointer to random string
//    saltLen     length of the "salt" string (bytes)
//    pSign       pointer to the signature string of the RSA length
//    pPrvKey     pointer to the RSA private key context
//    pPubKey     (optional) pointer to the RSA public key context
//    pMethod     hash method
//    pBuffer     pointer to scratch buffer
*F*/

/* clang-format off */
IPPFUN(IppStatus, ippsRSASign_PSS_rmf, (const Ipp8u* pMsg,
                                        int msgLen,
                                        const Ipp8u* pSalt,
                                        int saltLen,
                                        Ipp8u* pSign,
                                        const IppsRSAPrivateKeyState* pPrvKey,
                                        const IppsRSAPublicKeyState* pPubKey,
                                        const IppsHashMethod* pMethod,
                                        Ipp8u* pScratchBuffer))
/* clang-format on */
{
    // badargs and pointer alignments
    const IppStatus preprocResult = SingleSignPssRmfPreproc(pMsg,
                                                            msgLen,
                                                            pSalt,
                                                            saltLen,
                                                            pSign,
                                                            &pPrvKey,
                                                            &pPubKey,
                                                            pMethod,
                                                            pScratchBuffer);

    if (ippStsNoErr != preprocResult) {
        return preprocResult;
    }

    {
        Ipp8u hashMsg[MAX_HASH_SIZE];

        /* hash length */
        int hashLen = pMethod->hashLen;

        /* size of RSA modulus in bytes and chunks */
        cpSize rsaBits = RSA_PRV_KEY_BITSIZE_N(pPrvKey);
        cpSize k       = BITS2WORD8_SIZE(rsaBits);
        cpSize nsN     = BITS_BNU_CHUNK(rsaBits);

        /* align buffer */
        BNU_CHUNK_T* pBuffer =
            (BNU_CHUNK_T*)(IPP_ALIGNED_PTR(pScratchBuffer, (int)sizeof(BNU_CHUNK_T)));

        /* temporary BNs */
        __ALIGN8 IppsBigNumState bnC;
        __ALIGN8 IppsBigNumState bnP;

        /* message presentative size */
        int emBits = rsaBits - 1;
        int emLen  = BITS2WORD8_SIZE(emBits);

        /* size of padding string (PS) */
        int psLen = emLen - hashLen - saltLen - 2;

        /* test size consistence */
        if (0 > psLen)
            IPP_ERROR_RET(ippStsLengthErr);

        /* compute hash of the message */
        ippsHashMessage_rmf(pMsg, msgLen, hashMsg, pMethod);

        /* make BNs */
        BN_Make(pBuffer, pBuffer + nsN + 1, nsN, &bnC);
        pBuffer += (nsN + 1) * 2;
        BN_Make(pBuffer, pBuffer + nsN + 1, nsN, &bnP);
        pBuffer += (nsN + 1) * 2;

        /*
      // EMSA-PSS encoding
      */
        {
            Ipp8u* pM  = (Ipp8u*)BN_NUMBER(&bnP);
            Ipp8u* pEM = pSign;
            Ipp8u* pDB = pSign;
            int dbLen  = emLen - hashLen - 1;
            Ipp8u* pH  = pSign + dbLen;

            /* construct message M'
         // M' = (00 00 00 00 00 00 00 00) || mHash || salt
         // where:
         //    mHash = HASH(pMsg)
         */
            PadBlock(0, pM, 8);
            CopyBlock(hashMsg, pM + 8, hashLen);
            CopyBlock(pSalt, pM + 8 + hashLen, saltLen);

            /* construct EM
         // EM = maskedDB || H || 0xBC
         // where:
         //    H = HASH(M')
         //    maskedDB = DB ^ MGF(H)
         //    where:
         //       DB = PS || 0x01 || salt
         //
         // by other words
         // EM = (dbMask ^ (PS || 0x01 || salt)) || HASH(M) || 0xBC
         */
            pEM[emLen - 1] = 0xBC;                                       /* tail octet */
            ippsHashMessage_rmf(pM, 8 + hashLen + saltLen, pH, pMethod); /* H = HASH(M) */
            ippsMGF1_rmf(pH, hashLen, pDB, dbLen, pMethod);              /* dbMask = MGF(H) */

            XorBlock(pDB + psLen + 1, pSalt, pDB + psLen + 1, saltLen);
            pDB[psLen] ^= 0x01;

            /* make sure that top 8*emLen-emBits bits are clear */
            pDB[0] &= MAKEMASK32(8 - 8 * emLen + emBits);
        }

        /*
      // private-key operation
      */
        ippsSetOctString_BN(pSign, emLen, &bnC);

        if (RSA_PRV_KEY1_VALID_ID(pPrvKey))
            gsRSAprv_cipher(&bnP, &bnC, pPrvKey, pBuffer);
        else
            gsRSAprv_cipher_crt(&bnP, &bnC, pPrvKey, pBuffer);

        ippsGetOctString_BN(pSign, k, &bnP);

        /* no check requested */
        if (!pPubKey)
            return ippStsNoErr;

        /* check the result before send it out (fault attack mitigation) */
        else {
            gsRSApub_cipher(&bnP, &bnP, pPubKey, pBuffer);
            if (0 == cpBN_cmp(&bnP, &bnC))
                return ippStsNoErr;
            /* discard signature if check failed */
            else {
                PadBlock(0, pSign, k);
                return ippStsErr;
            }
        }
    }
}
