/*************************************************************************
* 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.
//     RSA Functions
//
//  Contents:
//        ippsRSA_ValidateKeys()
//
*/

#include "owndefs.h"
#include "owncp.h"
#include "pcpbn.h"
#include "pcpprimeg.h"
#include "pcpprng.h"
#include "pcpngrsa.h"

#include "pcpprime_isco.h"
#include "pcpprime_isprob.h"

/*F*
// Name: ippsRSA_ValidateKeys
//
// Purpose: Validate RSA keys
//
// Returns:                   Reason:
//    ippStsNullPtrErr           NULL == pPublicKey
//                               NULL == pPrivateKeyType2
//                               NULL == pPrivateKeyType1
//                               NULL == pBuffer
//                               NULL == pPrimeGen
//                               NULL == rndFunc
//                               NULL == pResult
//
//    ippStsContextMatchErr     !RSA_PUB_KEY_VALID_ID(pPublicKey)
//                              !RSA_PRV_KEY2_VALID_ID(pPrivateKeyType2)
//                              !RSA_PRV_KEY1_VALID_ID(pPrivateKeyType1)
//                              !PRIME_VALID_ID(pPrimeGen)
//
//    ippStsIncompleteContextErr public and.or private key is not set up
//
//    ippStsSizeErr              PRIME_MAXBITSIZE(pPrimeGen) < factorPbitSize
//
//    ippStsBadArgErr            nTrials < 1
//
//    ippStsNoErr                no error
//
// Parameters:
//    pResult           pointer to the validation result
//    pPublicKey        pointer to the public key context
//    pPrivateKeyType2  pointer to the private key type2 context
//    pPrivateKeyType1  (optional) pointer to the private key type1 context
//    pBuffer           pointer to the temporary buffer
//    nTrials           parameter of Miller-Rabin Test
//    pPrimeGen         pointer to the Prime generator context
//    rndFunc           external PRNG
//    pRndParam         pointer to the external PRNG parameters
*F*/
/*
// make sure D*E = 1 mod(phi(P,Q))
// where phi(P,Q) = (P-1)*(Q-1)
*/
static int isValidPriv1_classic(const BNU_CHUNK_T* pN,
                                int nsN,
                                const BNU_CHUNK_T* pE,
                                int nsE,
                                const BNU_CHUNK_T* pD,
                                int nsD,
                                const BNU_CHUNK_T* pFactorP,
                                int nsP,
                                const BNU_CHUNK_T* pFactorQ,
                                int nsQ,
                                BNU_CHUNK_T* pBuffer)
{
    BNU_CHUNK_T* pPhi     = pBuffer;
    BNU_CHUNK_T* pProduct = pPhi + nsN;
    BNU_CHUNK_T c         = cpSub_BNU(pPhi, pN, pFactorP, nsP);
    int prodLen;
    if (nsN > 1)
        cpDec_BNU(pPhi + nsP, pN + nsP, nsQ, c);
    c = cpSub_BNU(pPhi, pPhi, pFactorQ, nsQ);
    if (nsN > 1)
        cpDec_BNU(pPhi + nsQ, pPhi + nsQ, nsP, c);
    cpInc_BNU(pPhi, pPhi, nsP + nsQ, 1);

    cpMul_BNU_school(pProduct, pE, nsE, pD, nsD);
    prodLen = cpMod_BNU(pProduct, nsE + nsD, pPhi, nsN);

    return 1 == cpEqu_BNU_CHUNK(pProduct, prodLen, 1) ? IPP_IS_VALID : IPP_IS_INVALID;
}

/*
// make sure D*E = 1 mod(lcm(P-1,Q-1))
// where lcm(P-1,Q-1) = (P-1)*(Q-1)/gcd(P-1,Q-1)
*/
static int isValidPriv1_rsa(const BNU_CHUNK_T* pN,
                            int nsN,
                            const BNU_CHUNK_T* pE,
                            int nsE,
                            const BNU_CHUNK_T* pD,
                            int nsD,
                            BNU_CHUNK_T* pFactorP,
                            int nsP,
                            BNU_CHUNK_T* pFactorQ,
                            int nsQ,
                            BNU_CHUNK_T* pBuffer)
{
    __ALIGN8 IppsBigNumState tmpBN1;
    __ALIGN8 IppsBigNumState tmpBN2;
    __ALIGN8 IppsBigNumState tmpBN3;

    BNU_CHUNK_T* pProduct = pBuffer;
    BNU_CHUNK_T* pGcd     = pProduct + (nsN + 1);
    BNU_CHUNK_T* pLcm;
    int nsLcm;
    int prodLen;
    pBuffer = pGcd + (nsP + 1) * 2;

    /* P = P-1 and Q = Q-1 */
    pFactorP[0]--;
    pFactorQ[0]--;

    /* compute product (P-1)*(Q-1) = P*Q -P -Q +1 = N -(P-1) -(Q-1) -1 */
    {
        BNU_CHUNK_T c = cpSub_BNU(pProduct, pN, pFactorP, nsP);
        if (nsN > 1)
            cpDec_BNU(pProduct + nsP, pN + nsP, nsQ, c);
        c = cpSub_BNU(pProduct, pProduct, pFactorQ, nsQ);
        if (nsN > 1)
            cpDec_BNU(pProduct + nsQ, pProduct + nsQ, nsP, c);
        cpDec_BNU(pProduct, pProduct, nsN, 1);
    }

    /* compute gcd(p-1, q-1) */
    BN_Make(pGcd, pGcd + nsP + 1, nsP, &tmpBN1);        /* BN(gcd) */
    BN_SIZE(&tmpBN1) = nsP;
    BN_Make(pFactorP, pBuffer, nsP, &tmpBN2);           /* BN(P-1) */
    BN_SIZE(&tmpBN2) = nsP;
    BN_Make(pFactorQ, pBuffer + nsP + 1, nsQ, &tmpBN3); /* BN(Q-1) */
    BN_SIZE(&tmpBN3) = nsQ;
    ippsGcd_BN(&tmpBN2, &tmpBN3, &tmpBN1);

    /* compute lcm(p-1, q-1) = (p-1)(q-1)/gcd(p-1, q-1) */
    pLcm = pBuffer;
    cpDiv_BNU(pLcm, &nsLcm, pProduct, nsN, pGcd, BN_SIZE(&tmpBN1));

    /* test E*D = 1 mod lcm */
    cpMul_BNU_school(pProduct, pE, nsE, pD, nsD);
    prodLen = cpMod_BNU(pProduct, nsE + nsD, pLcm, nsLcm);

    /* restore P and Q */
    pFactorP[0]++;
    pFactorQ[0]++;

    return 1 == cpEqu_BNU_CHUNK(pProduct, prodLen, 1) ? IPP_IS_VALID : IPP_IS_INVALID;
}

/* clang-format off */
IPPFUN(IppStatus, ippsRSA_ValidateKeys, (int* pResult,
                                         const IppsRSAPublicKeyState* pPublicKey,
                                         const IppsRSAPrivateKeyState* pPrivateKeyType2,
                                         const IppsRSAPrivateKeyState* pPrivateKeyType1, /*optional */
                                         Ipp8u* pBuffer,
                                         int nTrials,
                                         IppsPrimeState* pPrimeGen, /*NULL */
                                         IppBitSupplier rndFunc, void* pRndParam))
/* clang-format on */
{
    IPP_BAD_PTR1_RET(pPublicKey);
    IPP_BADARG_RET(!RSA_PUB_KEY_VALID_ID(pPublicKey), ippStsContextMatchErr);
    IPP_BADARG_RET(!RSA_PUB_KEY_IS_SET(pPublicKey), ippStsIncompleteContextErr);

    IPP_BAD_PTR1_RET(pPrivateKeyType2);
    IPP_BADARG_RET(!RSA_PRV_KEY2_VALID_ID(pPrivateKeyType2), ippStsContextMatchErr);
    IPP_BADARG_RET(!RSA_PRV_KEY_IS_SET(pPrivateKeyType2), ippStsIncompleteContextErr);

    if (pPrivateKeyType1) { /* pPrivateKeyType1 is optional */
        IPP_BADARG_RET(!RSA_PRV_KEY1_VALID_ID(pPrivateKeyType1), ippStsContextMatchErr);
        IPP_BADARG_RET(!RSA_PRV_KEY_IS_SET(pPrivateKeyType1), ippStsIncompleteContextErr);
    }

    IPP_BAD_PTR3_RET(pResult, pBuffer, rndFunc);

    IPP_UNREFERENCED_PARAMETER(pPrimeGen);

    {
        /* E key component */
        BNU_CHUNK_T* pExpE = RSA_PUB_KEY_E(pPublicKey);
        cpSize nsE         = BITS_BNU_CHUNK(RSA_PUB_KEY_BITSIZE_E(pPublicKey));

        /* N key component */
        BNU_CHUNK_T* pN = MOD_MODULUS(RSA_PUB_KEY_NMONT(pPublicKey));
        cpSize nsN      = MOD_LEN(RSA_PUB_KEY_NMONT(pPublicKey));

        /* P, Q, dP, dQ, invQ key components */
        gsModEngine* pMontP   = RSA_PRV_KEY_PMONT(pPrivateKeyType2);
        gsModEngine* pMontQ   = RSA_PRV_KEY_QMONT(pPrivateKeyType2);
        BNU_CHUNK_T* pFactorP = MOD_MODULUS(pMontP);
        BNU_CHUNK_T* pFactorQ = MOD_MODULUS(pMontQ);
        BNU_CHUNK_T* pExpDp   = RSA_PRV_KEY_DP(pPrivateKeyType2);
        BNU_CHUNK_T* pExpDq   = RSA_PRV_KEY_DQ(pPrivateKeyType2);
        BNU_CHUNK_T* pInvQ    = RSA_PRV_KEY_INVQ(pPrivateKeyType2);

        int factorPbitSize = RSA_PRV_KEY_BITSIZE_P(pPrivateKeyType2);
        int factorQbitSize = RSA_PRV_KEY_BITSIZE_Q(pPrivateKeyType2);
        cpSize nsP         = MOD_LEN(RSA_PRV_KEY_PMONT(pPrivateKeyType2));
        cpSize nsQ         = MOD_LEN(RSA_PRV_KEY_QMONT(pPrivateKeyType2));

        int ret = IPP_IS_VALID;

        BNU_CHUNK_T* pTmp = (BNU_CHUNK_T*)(IPP_ALIGNED_PTR(pBuffer, (int)sizeof(BNU_CHUNK_T)));
        BNU_CHUNK_T* pFreeBuffer = pTmp + nsN;

        /* choose security parameter */
        int mrTrials = (nTrials < 1) ? MR_rounds_p80(factorPbitSize) : nTrials;

        IppStatus sts = ippStsNoErr;
        do {
            /* test if E is odd and 3 <= E < N */
            if (0 == (pExpE[0] & 1)) {
                ret = IPP_IS_INVALID;
                break;
            }
            if (1 == nsE && pExpE[0] < 3) {
                ret = IPP_IS_INVALID;
                break;
            }
            if (0 <= cpCmp_BNU(pExpE, nsE, pN, nsN)) {
                ret = IPP_IS_INVALID;
                break;
            }

            /* test if N==P*Q */
            cpMul_BNU_school(pFreeBuffer, pFactorP, nsP, pFactorQ, nsQ);
            {
                cpSize ns = cpFix_BNU(pFreeBuffer, nsP + nsQ);
                if (cpCmp_BNU(pFreeBuffer, ns, pN, nsN)) {
                    ret = IPP_IS_INVALID;
                    break;
                }
            }
            /* test if PubKey(N)==PrivKeytype2(N) */
            if (cpCmp_BNU(pN,
                          nsN,
                          MOD_MODULUS(RSA_PRV_KEY_NMONT(pPrivateKeyType2)),
                          MOD_LEN(RSA_PRV_KEY_NMONT(pPrivateKeyType2)))) {
                ret = IPP_IS_INVALID;
                break;
            }
            /* test if PubKey(N)==PrivKeytype1(N) */
            if (pPrivateKeyType1) {
                if (cpCmp_BNU(pN,
                              nsN,
                              MOD_MODULUS(RSA_PRV_KEY_NMONT(pPrivateKeyType1)),
                              MOD_LEN(RSA_PRV_KEY_NMONT(pPrivateKeyType1)))) {
                    ret = IPP_IS_INVALID;
                    break;
                }
            }

            /* test if P is prime */
            //if(0==cpIsProbablyPrime(pFactorP, factorPbitSize, mrTrials,
            //                        rndFunc, pRndParam,
            //                        pMontP, pFreeBuffer)) {
            //   ret = IPP_IS_COMPOSITE;
            //   break;
            //}
            {
                int r = cpIsProbablyPrime(pFactorP,
                                          factorPbitSize,
                                          mrTrials,
                                          rndFunc,
                                          pRndParam,
                                          pMontP,
                                          pFreeBuffer);
                if (0 > r) {
                    sts = ippStsErr;
                    break;
                }
                if (0 == r) {
                    ret = IPP_IS_COMPOSITE;
                    break;
                }
            }
            /* test if E and (P-1) co-prime */
            cpDec_BNU(pTmp, pFactorP, nsP, 1);
            if (0 == cpIsCoPrime(pExpE, nsE, pTmp, nsP, pFreeBuffer)) {
                ret = IPP_IS_INVALID;
                break;
            }
            /* test if E*dP = 1 mod (P-1) */
            cpMul_BNU_school(pFreeBuffer, pExpDp, nsP, pExpE, nsE);
            cpMod_BNU(pFreeBuffer, nsP + nsE, pTmp, nsP);
            if (!cpEqu_BNU_CHUNK(pFreeBuffer, nsP, 1)) {
                ret = IPP_IS_INVALID;
                break;
            }

            /* test if Q is prime */
            //if(0==cpIsProbablyPrime(pFactorQ, factorQbitSize, mrTrials,
            //                        rndFunc, pRndParam,
            //                        pMontQ, pFreeBuffer)) {
            //   ret = IPP_IS_COMPOSITE;
            //   break;
            //}
            {
                int r = cpIsProbablyPrime(pFactorQ,
                                          factorQbitSize,
                                          mrTrials,
                                          rndFunc,
                                          pRndParam,
                                          pMontQ,
                                          pFreeBuffer);
                if (0 > r) {
                    sts = ippStsErr;
                    break;
                }
                if (0 == r) {
                    ret = IPP_IS_COMPOSITE;
                    break;
                }
            }
            /* test if E and (Q-1) co-prime */
            cpDec_BNU(pTmp, pFactorQ, nsQ, 1);
            if (0 == cpIsCoPrime(pExpE, nsE, pTmp, nsQ, pFreeBuffer)) {
                ret = IPP_IS_INVALID;
                break;
            }
            /* test if E*dQ = 1 mod (Q-1) */
            cpMul_BNU_school(pFreeBuffer, pExpDq, nsQ, pExpE, nsE);
            cpMod_BNU(pFreeBuffer, nsQ + nsE, pTmp, nsQ);
            if (!cpEqu_BNU_CHUNK(pFreeBuffer, nsQ, 1)) {
                ret = IPP_IS_INVALID;
                break;
            }

            /* test if 1==(Q*Qinv) mod P */
            cpMul_BNU_school(pFreeBuffer, pInvQ, nsP, pFactorQ, nsQ);
            cpMod_BNU(pFreeBuffer, nsP + nsQ, pFactorP, nsP);
            if (!cpEqu_BNU_CHUNK(pFreeBuffer, nsP, 1)) {
                ret = IPP_IS_INVALID;
                break;
            }

            /* test private exponent (optional) */
            if (pPrivateKeyType1) {
                const BNU_CHUNK_T* pExpD = RSA_PRV_KEY_D(pPrivateKeyType1);
                cpSize nsD               = nsN;
                int resilt1              = isValidPriv1_classic(pN,
                                                   nsN,
                                                   pExpE,
                                                   nsE,
                                                   pExpD,
                                                   nsD,
                                                   pFactorP,
                                                   nsP,
                                                   pFactorQ,
                                                   nsQ,
                                                   pTmp);
                int resilt2              = isValidPriv1_rsa(pN,
                                               nsN,
                                               pExpE,
                                               nsE,
                                               pExpD,
                                               nsD,
                                               pFactorP,
                                               nsP,
                                               pFactorQ,
                                               nsQ,
                                               pTmp);
                if (IPP_IS_VALID != resilt1 && IPP_IS_VALID != resilt2) {
                    ret = IPP_IS_INVALID;
                    break;
                }
            }
        } while (0);

        *pResult = ret;
        return sts;
    }
}
