/********************************************************************************/
/*										*/
/*			 TPM to OpenSSL BigNum Shim Layer			*/
/*			     Written by Ken Goldman				*/
/*		       IBM Thomas J. Watson Research Center			*/
/*            $Id: TpmToOsslMath.c 1598 2020-03-27 21:59:49Z kgoldman $		*/
/*										*/
/*  Licenses and Notices							*/
/*										*/
/*  1. Copyright Licenses:							*/
/*										*/
/*  - Trusted Computing Group (TCG) grants to the user of the source code in	*/
/*    this specification (the "Source Code") a worldwide, irrevocable, 		*/
/*    nonexclusive, royalty free, copyright license to reproduce, create 	*/
/*    derivative works, distribute, display and perform the Source Code and	*/
/*    derivative works thereof, and to grant others the rights granted herein.	*/
/*										*/
/*  - The TCG grants to the user of the other parts of the specification 	*/
/*    (other than the Source Code) the rights to reproduce, distribute, 	*/
/*    display, and perform the specification solely for the purpose of 		*/
/*    developing products based on such documents.				*/
/*										*/
/*  2. Source Code Distribution Conditions:					*/
/*										*/
/*  - Redistributions of Source Code must retain the above copyright licenses, 	*/
/*    this list of conditions and the following disclaimers.			*/
/*										*/
/*  - Redistributions in binary form must reproduce the above copyright 	*/
/*    licenses, this list of conditions	and the following disclaimers in the 	*/
/*    documentation and/or other materials provided with the distribution.	*/
/*										*/
/*  3. Disclaimers:								*/
/*										*/
/*  - THE COPYRIGHT LICENSES SET FORTH ABOVE DO NOT REPRESENT ANY FORM OF	*/
/*  LICENSE OR WAIVER, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, WITH	*/
/*  RESPECT TO PATENT RIGHTS HELD BY TCG MEMBERS (OR OTHER THIRD PARTIES)	*/
/*  THAT MAY BE NECESSARY TO IMPLEMENT THIS SPECIFICATION OR OTHERWISE.		*/
/*  Contact TCG Administration (admin@trustedcomputinggroup.org) for 		*/
/*  information on specification licensing rights available through TCG 	*/
/*  membership agreements.							*/
/*										*/
/*  - THIS SPECIFICATION IS PROVIDED "AS IS" WITH NO EXPRESS OR IMPLIED 	*/
/*    WARRANTIES WHATSOEVER, INCLUDING ANY WARRANTY OF MERCHANTABILITY OR 	*/
/*    FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS, OR 		*/
/*    NONINFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS, OR ANY WARRANTY 		*/
/*    OTHERWISE ARISING OUT OF ANY PROPOSAL, SPECIFICATION OR SAMPLE.		*/
/*										*/
/*  - Without limitation, TCG and its members and licensors disclaim all 	*/
/*    liability, including liability for infringement of any proprietary 	*/
/*    rights, relating to use of information in this specification and to the	*/
/*    implementation of this specification, and TCG disclaims all liability for	*/
/*    cost of procurement of substitute goods or services, lost profits, loss 	*/
/*    of use, loss of data or any incidental, consequential, direct, indirect, 	*/
/*    or special damages, whether under contract, tort, warranty or otherwise, 	*/
/*    arising in any way out of use or reliance upon this specification or any 	*/
/*    information herein.							*/
/*										*/
/*  (c) Copyright IBM Corp. and others, 2016 - 2020				*/
/*										*/
/********************************************************************************/

/* B.2.3.2. TpmToOsslMath.c */
/* B.2.3.2.1. Introduction */

/* The functions in this file provide the low-level interface between the TPM code and the big
   number and elliptic curve math routines in OpenSSL. */
/* Most math on big numbers require a context. The context contains the memory in which OpenSSL
   creates and manages the big number values. When a OpenSSL math function will be called that
   modifies a BIGNUM value, that value must be created in an OpenSSL context. The first line of code
   in such a function must be: OSSL_ENTER(); and the last operation before returning must be
   OSSL_LEAVE(). OpenSSL variables can then be created with BnNewVariable(). Constant values to be
   used by OpenSSL are created from the bigNum values passed to the functions in this file. Space
   for the BIGNUM control block is allocated in the stack of the function and then it is initialized
   by calling BigInitialized(). That function sets up the values in the BIGNUM structure and sets
   the data pointer to point to the data in the bignum_t. This is only used when the value is known
   to be a constant in the called function. */
/* Because the allocations of constants is on the local stack and the OSSL_ENTER()/OSSL_LEAVE() pair
   flushes everything created in OpenSSL memory, there should be no chance of a memory leak. */


#include "Tpm.h"
#ifdef MATH_LIB_OSSL
#include "TpmToOsslMath_fp.h"

/* B.2.3.2.3.1.	OsslToTpmBn() */
/* This function converts an OpenSSL BIGNUM to a TPM bignum. In this implementation it is assumed
   that OpenSSL uses a different control structure but the same data layout -- an array of
   native-endian words in little-endian order. */
/* Return Value	Meaning */
/* TRUE(1)	success */
/* FALSE(0)	failure because value will not fit or OpenSSL variable doesn't exist */
BOOL
OsslToTpmBn(
	    bigNum          bn,
	    BIGNUM          *osslBn
	    )
{
    VERIFY(osslBn != NULL);
    // If the bn is NULL, it means that an output value pointer was NULL meaning that
    // the results is simply to be discarded.
    if(bn != NULL)
	{
	    int         i;
	    //
	    VERIFY((unsigned)osslBn->top <= BnGetAllocated(bn));
	    for(i = 0; i < osslBn->top; i++)
		bn->d[i] = osslBn->d[i];
	    BnSetTop(bn, osslBn->top);
	}
    return TRUE;
 Error:
    return FALSE;
}

/* B.2.3.2.3.2.	BigInitialized() */
/* This function initializes an OSSL BIGNUM from a TPM bigConst. Do not use this for values that are
   passed to OpenSLL when they are not declared as const in the function prototype. Instead, use
   BnNewVariable(). */
BIGNUM *
BigInitialized(
	       BIGNUM             *toInit,
	       bigConst            initializer
	       )
{
    if(initializer == NULL)
	FAIL(FATAL_ERROR_PARAMETER);
    if(toInit == NULL || initializer == NULL)
	return NULL;
    toInit->d = (BN_ULONG *)&initializer->d[0];
    toInit->dmax = (int)initializer->allocated;
    toInit->top = (int)initializer->size;
    toInit->neg = 0;
    toInit->flags = 0;
    return toInit;
}

#ifndef OSSL_DEBUG
#   define BIGNUM_PRINT(label, bn, eol)
#   define DEBUG_PRINT(x)
#else
#   define DEBUG_PRINT(x)   printf("%s", x)
#   define BIGNUM_PRINT(label, bn, eol) BIGNUM_print((label), (bn), (eol))

static
void BIGNUM_print(
		  const char      *label,
		  const BIGNUM    *a,
		  BOOL             eol
		  )
{
    BN_ULONG        *d;
    int              i;
    int              notZero = FALSE;
    if(label != NULL)
	printf("%s", label);
    if(a == NULL)
	{
	    printf("NULL");
	    goto done;
	}
    if (a->neg)
	printf("-");
    for(i = a->top, d = &a->d[i - 1]; i > 0; i--)
	{
	    int         j;
	    BN_ULONG    l = *d--;
	    for(j = BN_BITS2 - 8; j >= 0; j -= 8)
		{
		    BYTE    b = (BYTE)((l >> j) & 0xFF);
		    notZero = notZero || (b != 0);
		    if(notZero)
			printf("%02x", b);
		}
	    if(!notZero)
		printf("0");
	}
 done:
    if(eol)
	printf("\n");
    return;
}
#endif

/* B.2.3.2.3.4. BnNewVariable() */
/* This function allocates a new variable in the provided context. If the context does not exist or
   the allocation fails, it is a catastrophic failure. */
static BIGNUM *
BnNewVariable(
	      BN_CTX          *CTX
	      )
{
    BIGNUM          *new;
    //
    // This check is intended to protect against calling this function without
    // having initialized the CTX.
    if((CTX == NULL) || ((new = BN_CTX_get(CTX)) == NULL))
	FAIL(FATAL_ERROR_ALLOCATION);
    return new;
}

#if LIBRARY_COMPATIBILITY_CHECK

BOOL
MathLibraryCompatibilityCheck(
			      void
			      )
{
    OSSL_ENTER();
    BIGNUM              *osslTemp = BnNewVariable(CTX);
    crypt_uword_t        i;
    BYTE                 test[] = {0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18,
				   0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
				   0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
				   0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00};
    BN_VAR(tpmTemp, sizeof(test) * 8); // allocate some space for a test value
    //
    // Convert the test data to a bigNum
    BnFromBytes(tpmTemp, test, sizeof(test));
    // Convert the test data to an OpenSSL BIGNUM
    BN_bin2bn(test, sizeof(test), osslTemp);
    // Make sure the values are consistent
    VERIFY(osslTemp->top == (int)tpmTemp->size);
    for(i = 0; i < tpmTemp->size; i++)
	VERIFY(osslTemp->d[i] == tpmTemp->d[i]);
    OSSL_LEAVE();
    return 1;
 Error:
    return 0;
}

#endif

/* B.2.3.2.3.3. BnModMult() */
/* Does multiply and divide returning the remainder of the divide. */
/* Return Value	Meaning */
/* TRUE(1)	success */
/* FALSE(0)	failure in operation */

LIB_EXPORT BOOL
BnModMult(
	  bigNum              result,
	  bigConst            op1,
	  bigConst            op2,
	  bigConst            modulus
	  )
{
    OSSL_ENTER();
    BOOL                OK = TRUE;
    BIGNUM              *bnResult = BN_NEW();
    BIGNUM              *bnTemp = BN_NEW();
    BIG_INITIALIZED(bnOp1, op1);
    BIG_INITIALIZED(bnOp2, op2);
    BIG_INITIALIZED(bnMod, modulus);
    //
    VERIFY(BN_mul(bnTemp, bnOp1, bnOp2, CTX));
    VERIFY(BN_div(NULL, bnResult, bnTemp, bnMod, CTX));
    VERIFY(OsslToTpmBn(result, bnResult));
    goto Exit;
 Error:
    OK = FALSE;
 Exit:
    OSSL_LEAVE();
    return OK;
}

/* B.2.3.2.3.4. BnMult() */
/* Multiplies two numbers */
/* Return Value	Meaning */
/* TRUE(1)	success */
/* FALSE(0)	failure in operation */

LIB_EXPORT BOOL
BnMult(
       bigNum               result,
       bigConst             multiplicand,
       bigConst             multiplier
       )
{
    OSSL_ENTER();
    BIGNUM              *bnTemp = BN_NEW();
    BOOL                 OK = TRUE;
    BIG_INITIALIZED(bnA, multiplicand);
    BIG_INITIALIZED(bnB, multiplier);
    //
    VERIFY(BN_mul(bnTemp, bnA, bnB, CTX));
    VERIFY(OsslToTpmBn(result, bnTemp));
    goto Exit;
 Error:
    OK = FALSE;
 Exit:
    OSSL_LEAVE();
    return OK;
}

/* B.2.3.2.3.5. BnDiv() */
/* This function divides two bigNum values. The function returns FALSE if there is an error in the
   operation. */
/* Return Value	Meaning */
/* TRUE(1)	success */
/* FALSE(0)	failure in operation */

LIB_EXPORT BOOL
BnDiv(
      bigNum               quotient,
      bigNum               remainder,
      bigConst             dividend,
      bigConst             divisor
      )
{
    OSSL_ENTER();
    BIGNUM              *bnQ = BN_NEW();
    BIGNUM              *bnR = BN_NEW();
    BOOL                 OK = TRUE;
    BIG_INITIALIZED(bnDend, dividend);
    BIG_INITIALIZED(bnSor, divisor);
    //
    if(BnEqualZero(divisor))
	FAIL(FATAL_ERROR_DIVIDE_ZERO);
    VERIFY(BN_div(bnQ, bnR, bnDend, bnSor, CTX));
    VERIFY(OsslToTpmBn(quotient, bnQ));
    VERIFY(OsslToTpmBn(remainder, bnR));
    DEBUG_PRINT("In BnDiv:\n");
    BIGNUM_PRINT("   bnDividend: ", bnDend, TRUE);
    BIGNUM_PRINT("    bnDivisor: ", bnSor, TRUE);
    BIGNUM_PRINT("   bnQuotient: ", bnQ, TRUE);
    BIGNUM_PRINT("  bnRemainder: ", bnR, TRUE);
    goto Exit;
 Error:
    OK = FALSE;
 Exit:
    OSSL_LEAVE();
    return OK;
}

#if ALG_RSA
/* B.2.3.2.3.6. BnGcd() */
/* Get the greatest common divisor of two numbers */
/* Return Value	Meaning */
/* TRUE(1)	success */
/* FALSE(0)	failure in operation */

LIB_EXPORT BOOL
BnGcd(
      bigNum      gcd,            // OUT: the common divisor
      bigConst    number1,        // IN:
      bigConst    number2         // IN:
      )
{
    OSSL_ENTER();
    BIGNUM              *bnGcd = BN_NEW();
    BOOL                 OK = TRUE;
    BIG_INITIALIZED(bn1, number1);
    BIG_INITIALIZED(bn2, number2);
    //
    VERIFY(BN_gcd(bnGcd, bn1, bn2, CTX));
    VERIFY(OsslToTpmBn(gcd, bnGcd));
    goto Exit;
 Error:
    OK = FALSE;
 Exit:
    OSSL_LEAVE();
    return OK;
}

/* B.2.3.2.3.7. BnModExp() */
/* Do modular exponentiation using bigNum values. The conversion from a bignum_t to a bigNum is
   trivial as they are based on the same structure */
/* Return Value	Meaning */
/* TRUE(1)	success */
/* FALSE(0)	failure in operation */

LIB_EXPORT BOOL
BnModExp(
	 bigNum               result,         // OUT: the result
	 bigConst             number,         // IN: number to exponentiate
	 bigConst             exponent,       // IN:
	 bigConst             modulus         // IN:
	 )
{
    OSSL_ENTER();
    BIGNUM              *bnResult = BN_NEW();
    BOOL                 OK = TRUE;
    BIG_INITIALIZED(bnN, number);
    BIG_INITIALIZED(bnE, exponent);
    BIG_INITIALIZED(bnM, modulus);
    //
    VERIFY(BN_mod_exp(bnResult, bnN, bnE, bnM, CTX));
    VERIFY(OsslToTpmBn(result, bnResult));
    goto Exit;
 Error:
    OK = FALSE;
 Exit:
    OSSL_LEAVE();
    return OK;
}

/* B.2.3.2.3.8. BnModInverse() */
/* Modular multiplicative inverse */
/* Return Value	Meaning */
/* TRUE(1)	success */
/* FALSE(0)	failure in operation */

LIB_EXPORT BOOL
BnModInverse(
	     bigNum               result,
	     bigConst             number,
	     bigConst             modulus
	     )
{
    OSSL_ENTER();
    BIGNUM              *bnResult = BN_NEW();
    BOOL                 OK = TRUE;
    BIG_INITIALIZED(bnN, number);
    BIG_INITIALIZED(bnM, modulus);
    //
    VERIFY(BN_mod_inverse(bnResult, bnN, bnM, CTX) != NULL);
    VERIFY(OsslToTpmBn(result, bnResult));
    goto Exit;
 Error:
    OK = FALSE;
 Exit:
    OSSL_LEAVE();
    return OK;
}

#endif // TPM_ALG_RSA

#if ALG_ECC

/* B.2.3.2.3.9. PointFromOssl() */
/* Function to copy the point result from an OSSL function to a bigNum */
/* Return Value	Meaning */
/* TRUE(1)	success */
/* FALSE(0)	failure in operation */
static BOOL
PointFromOssl(
	      bigPoint         pOut,      // OUT: resulting point
	      EC_POINT        *pIn,       // IN: the point to return
	      bigCurve         E          // IN: the curve
	      )
{
    BIGNUM         *x = NULL;
    BIGNUM         *y = NULL;
    BOOL            OK;
    BN_CTX_start(E->CTX);
    //
    x = BN_CTX_get(E->CTX);
    y = BN_CTX_get(E->CTX);
    if(y == NULL)
	FAIL(FATAL_ERROR_ALLOCATION);
    // If this returns false, then the point is at infinity
    OK = EC_POINT_get_affine_coordinates_GFp(E->G, pIn, x, y, E->CTX);
    if(OK)
	{
	    OsslToTpmBn(pOut->x, x);
	    OsslToTpmBn(pOut->y, y);
	    BnSetWord(pOut->z, 1);
	}
    else
	BnSetWord(pOut->z, 0);
    BN_CTX_end(E->CTX);
    return OK;
}
/* B.2.3.2.3.10. EcPointInitialized() */
/* Allocate and initialize a point. */

static EC_POINT *
EcPointInitialized(
		   pointConst          initializer,
		   bigCurve            E
		   )
{
    EC_POINT            *P = NULL;
    
    if(initializer != NULL)
	{
	    BIG_INITIALIZED(bnX, initializer->x);
	    BIG_INITIALIZED(bnY, initializer->y);
	    if(E == NULL)
		FAIL(FATAL_ERROR_ALLOCATION);
	    P = EC_POINT_new(E->G);
	    if(!EC_POINT_set_affine_coordinates_GFp(E->G, P, bnX, bnY, E->CTX))
		P = NULL;
	}
    return P;
}

/* B.2.3.2.3.11. BnCurveInitialize() */
/* This function initializes the OpenSSL group definition */
/* It is a fatal error if groupContext is not provided. */
/* Return Values Meaning */
/* NULL the TPM_ECC_CURVE is not valid */
/* non-NULL points to a structure in groupContext */

LIB_EXPORT bigCurve
BnCurveInitialize(
		  bigCurve          E,           // IN: curve structure to initialize
		  TPM_ECC_CURVE     curveId      // IN: curve identifier
		  )
{
    const ECC_CURVE_DATA    *C = GetCurveData(curveId);
    if(C == NULL)
	E = NULL;
    if(E != NULL)
	{
	    // This creates the OpenSSL memory context that stays in effect as long as the
	    // curve (E) is defined.
	    OSSL_ENTER();                       // if the allocation fails, the TPM fails
	    EC_POINT                *P = NULL;
	    BIG_INITIALIZED(bnP, C->prime);
	    BIG_INITIALIZED(bnA, C->a);
	    BIG_INITIALIZED(bnB, C->b);
	    BIG_INITIALIZED(bnX, C->base.x);
	    BIG_INITIALIZED(bnY, C->base.y);
	    BIG_INITIALIZED(bnN, C->order);
	    BIG_INITIALIZED(bnH, C->h);
	    //
	    E->C = C;
	    E->CTX = CTX;
	    
	    // initialize EC group, associate a generator point and initialize the point
	    // from the parameter data
	    // Create a group structure
	    E->G = EC_GROUP_new_curve_GFp(bnP, bnA, bnB, CTX);
	    VERIFY(E->G != NULL);
	    
	    // Allocate a point in the group that will be used in setting the
	    // generator. This is not needed after the generator is set.
	    P = EC_POINT_new(E->G);
	    VERIFY(P != NULL);
	    
	    // Need to use this in case Montgomery method is being used
	    VERIFY(EC_POINT_set_affine_coordinates_GFp(E->G, P, bnX, bnY, CTX));
	    // Now set the generator
	    VERIFY(EC_GROUP_set_generator(E->G, P, bnN, bnH));
	    
	    EC_POINT_free(P);
	    goto Exit;
	Error:
	    EC_POINT_free(P);
	    BnCurveFree(E);
	    E = NULL;
	}
 Exit:
    return E;
}

/* B.2.3.2.3.15.	BnCurveFree() */
/* This function will free the allocated components of the curve and end the frame in which the
   curve data exists */
LIB_EXPORT void
BnCurveFree(
	    bigCurve E
	    )
{
    if(E)
	{
	    EC_GROUP_free(E->G);
	    OsslContextLeave(E->CTX);
	}
}

/* B.2.3.2.3.11. BnEccModMult() */
/* This functi2n does a point multiply of the form R = [d]S */
/* Return Values Meaning */
/* FALSE failure in operation; treat as result being point at infinity */

LIB_EXPORT BOOL
BnEccModMult(
	     bigPoint             R,         // OUT: computed point
	     pointConst           S,         // IN: point to multiply by 'd' (optional)
	     bigConst             d,         // IN: scalar for [d]S
	     bigCurve             E
	     )
{
    EC_POINT            *pR = EC_POINT_new(E->G);
    EC_POINT            *pS = EcPointInitialized(S, E);
    BIG_INITIALIZED(bnD, d);
    
    if(S == NULL)
	EC_POINT_mul(E->G, pR, bnD, NULL, NULL, E->CTX);
    else
	EC_POINT_mul(E->G, pR, NULL, pS, bnD, E->CTX);
    PointFromOssl(R, pR, E);
    EC_POINT_free(pR);
    EC_POINT_free(pS);
    return !BnEqualZero(R->z);
}

/* B.2.3.2.3.13. BnEccModMult2() */
/* This function does a point multiply of the form R = [d]G + [u]Q */
/* FALSE	failure in operation; treat as result being point at infinity */

LIB_EXPORT BOOL
BnEccModMult2(
	      bigPoint             R,         // OUT: computed point
	      pointConst           S,         // IN: optional point
	      bigConst             d,         // IN: scalar for [d]S or [d]G
	      pointConst           Q,         // IN: second point
	      bigConst             u,         // IN: second scalar
	      bigCurve             E          // IN: curve
	      )
{
    EC_POINT            *pR = EC_POINT_new(E->G);
    EC_POINT            *pS = EcPointInitialized(S, E);
    BIG_INITIALIZED(bnD, d);
    EC_POINT            *pQ = EcPointInitialized(Q, E);
    BIG_INITIALIZED(bnU, u);
    
    if(S == NULL || S == (pointConst)&(AccessCurveData(E)->base))
	EC_POINT_mul(E->G, pR, bnD, pQ, bnU, E->CTX);
    else
	{
	    const EC_POINT        *points[2];
	    const BIGNUM          *scalars[2];
	    points[0] = pS;
	    points[1] = pQ;
	    scalars[0] = bnD;
	    scalars[1] = bnU;
	    EC_POINTs_mul(E->G, pR, NULL, 2, points, scalars, E->CTX);
	}
    PointFromOssl(R, pR, E);
    EC_POINT_free(pR);
    EC_POINT_free(pS);
    EC_POINT_free(pQ);
    return !BnEqualZero(R->z);
}

/* B.2.3.2.4. BnEccAdd() */
/* This function does addition of two points. */
/* Return Values Meaning */
/* FALSE failure in operation; treat as result being point at infinity */
LIB_EXPORT BOOL
BnEccAdd(
	 bigPoint             R,         // OUT: computed point
	 pointConst           S,         // IN: point to multiply by 'd'
	 pointConst           Q,         // IN: second point
	 bigCurve             E          // IN: curve
	 )
{
    EC_POINT            *pR = EC_POINT_new(E->G);
    EC_POINT            *pS = EcPointInitialized(S, E);
    EC_POINT            *pQ = EcPointInitialized(Q, E);
    //
    EC_POINT_add(E->G, pR, pS, pQ, E->CTX);
    PointFromOssl(R, pR, E);
    EC_POINT_free(pR);
    EC_POINT_free(pS);
    EC_POINT_free(pQ);
    return !BnEqualZero(R->z);
}

#endif // ALG_ECC
#endif // MATH_LIB_OSSL
