/*************************************************************************
* 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:
//        ippsRSAVerify_PSS()
//
*/

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

////////////////////////////////////////////////////////////////////////////////////////////////////
/*F*
// Name: ippsRSAVerify_PSS
//
// Purpose: Performs Signature Verification according to RSASSA-PSS
//
// Returns:                   Reason:
//    ippStsNotSupportedModeErr  invalid hashAlg value
//
//    ippStsNullPtrErr           NULL == pMsg
//                               NULL == pSign
//                               NULL == pIsValid
//                               NULL == pKey
//                               NULL == pBuffer
//
//    ippStsLengthErr            msgLen<0
//                               RSAsize <=hashLen +2
//
//    ippStsContextMatchErr      !RSA_PUB_KEY_VALID_ID()
//
//    ippStsIncompleteContextErr public key is not set up
//
//    ippStsNoErr                no error
//
// Parameters:
//    pMsg        pointer to the message to be verified
//    msgLen      length of the message
//    pSign       pointer to the signature string of the RSA length
//    pIsValid    pointer to the verification result
//    pKey        pointer to the RSA public key context
//    hashAlg     hash ID
//    pBuffer     pointer to scratch buffer
*F*/
/* clang-format off */
IPPFUN(IppStatus, ippsRSAVerify_PSS, (const Ipp8u* pMsg,
                                      int msgLen,
                                      const Ipp8u* pSign,
                                      int* pIsValid,
                                      const IppsRSAPublicKeyState* pKey,
                                      IppHashAlgId hashAlg,
                                      Ipp8u* pScratchBuffer))
/* clang-format on */
{
    /* test hash algorithm ID */
    hashAlg = cpValidHashAlg(hashAlg);
    IPP_BADARG_RET(ippHashAlg_Unknown == hashAlg, ippStsNotSupportedModeErr);
    /* check if the algorithm is from the sha3 family (SHA3 is not supported in non-rmf methods)*/
    IPP_BADARG_RET(cpIsSHA3AlgID(hashAlg), ippStsNotSupportedModeErr);

    /* test message length */
    IPP_BADARG_RET((msgLen < 0), ippStsLengthErr);
    /* test message pointer */
    IPP_BADARG_RET((msgLen && !pMsg), ippStsNullPtrErr);

    /* test data pointer */
    IPP_BAD_PTR2_RET(pSign, pIsValid);

    /* test public key context */
    IPP_BAD_PTR2_RET(pKey, pScratchBuffer);
    IPP_BADARG_RET(!RSA_PUB_KEY_VALID_ID(pKey), ippStsContextMatchErr);
    IPP_BADARG_RET(!RSA_PUB_KEY_IS_SET(pKey), ippStsIncompleteContextErr);

    {
        Ipp8u hashMsg[MAX_HASH_SIZE];

        /* hash length */
        int hashLen = cpHashSize(hashAlg);

        /* size of RSA modulus in bytes and chunks */
        cpSize rsaBits = RSA_PUB_KEY_BITSIZE_N(pKey);
        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);

        /* test size consistence */
        if (k <= (hashLen + 2))
            IPP_ERROR_RET(ippStsLengthErr);

        /* compute hash of the message */
        ippsHashMessage(pMsg, msgLen, hashMsg, hashAlg);

        /* 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;

        /*
      // public-key operation
      */
        ippsSetOctString_BN(pSign, k, &bnP);
        gsRSApub_cipher(&bnC, &bnP, pKey, pBuffer);

        *pIsValid = 0;
        /*
      // EMSA-PSS verification
      */
        {
            /* convert BN into octet string EM
         // EM = maskedDB || H || 0xBC
         */
            Ipp8u* pEM = (Ipp8u*)BN_BUFFER(&bnC);
            ippsGetOctString_BN(pEM, emLen, &bnC);

            /* test last byte and top of (8*emLen-emBits) bits */
            if (0xBC == pEM[emLen - 1] && 0x00 == (pEM[0] >> (8 - (8 * emLen - emBits)))) {
                int psLen;
                Ipp8u* pM = (Ipp8u*)BN_NUMBER(&bnP);

                /* pointers to the EM fields */
                int dbLen  = emLen - hashLen - 1;
                Ipp8u* pDB = pEM;
                Ipp8u* pH  = pEM + dbLen;

                /* recover DB = maskedDB ^ MGF(H) */
                ippsMGF(pH, hashLen, pM, dbLen, hashAlg);
                XorBlock(pDB, pM, pDB, dbLen);

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

                /* skip over padding string (PS) */
                for (psLen = 0; psLen < dbLen; psLen++)
                    if (pDB[psLen])
                        break;

                /* and test non-zero octet */
                if (psLen < (dbLen) && 0x01 == pEM[psLen]) {

                    int saltLen = dbLen - 1 - psLen;

                    /* 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(pDB + psLen + 1, pM + 8 + hashLen, saltLen);

                    /* H' = HASH(M') */
                    ippsHashMessage(pM, 8 + hashLen + saltLen, pM, hashAlg);

                    /* compare H ~ H' */
                    *pIsValid = EquBlock(pH, pM, hashLen);
                }
            }
        }

        return ippStsNoErr;
    }
}
