Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

1082 lines
38 KiB
C

/*H********************************************************************************/
/*!
\File cryptnist.c
\Description
This module implements the math for elliptic curve cryptography
using curves in short Weierstrass form (NIST curves)
\Copyright
Copyright (c) Electronic Arts 2017. ALL RIGHTS RESERVED.
*/
/********************************************************************************H*/
/*** Include files ****************************************************************/
#include <string.h>
#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/crypt/crypthash.h"
#include "DirtySDK/crypt/crypthmac.h"
#include "DirtySDK/crypt/cryptrand.h"
#include "DirtySDK/crypt/cryptnist.h"
/*** Defines **********************************************************************/
//! the marker that marks uncompressed point format
#define CRYPTNIST_UNCOMPRESSED_MARKER (0x04)
//! number of iterations to do per call
#if !defined(CRYPTNIST_NUM_ITERATIONS)
#define CRYPTNIST_NUM_ITERATIONS (0x10)
#endif
//! memgroup for allocating the table
#define CRYPTNIST_MEMID ('nist')
/*** Type Definitions *************************************************************/
//! defintion of a curve, basis of initialization into big numbers
typedef struct NistCurveT
{
int32_t iIdent;
int32_t iSize;
uint8_t aPrime[48];
uint8_t aCoefficientA[48];
uint8_t aCoefficientB[48];
uint8_t aBasePoint[97];
uint8_t aOrder[48];
} NistCurveT;
/*** Variables ********************************************************************/
//! used to easily checked for default points
static const CryptEccPointT _Zero = { { { 0x0 }, 1, 0 }, { { 0x0 }, 1, 0 } };
//! the curves we support
static const NistCurveT _aEccCurves[] =
{
{
CRYPTCURVE_SECP256R1, 32,
{ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
{ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC },
{ 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3, 0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27, 0xD2, 0x60, 0x4B },
{ 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96,
0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5 },
{ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51 }
},
{
CRYPTCURVE_SECP384R1, 48,
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF },
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC },
{ 0xB3, 0x31, 0x2F, 0xA7, 0xE2, 0x3E, 0xE7, 0xE4, 0x98, 0x8E, 0x05, 0x6B, 0xE3, 0xF8, 0x2D, 0x19, 0x18, 0x1D, 0x9C, 0x6E, 0xFE, 0x81, 0x41, 0x12, 0x03, 0x14, 0x08, 0x8F, 0x50, 0x13, 0x87, 0x5A,
0xC6, 0x56, 0x39, 0x8D, 0x8A, 0x2E, 0xD1, 0x9D, 0x2A, 0x85, 0xC8, 0xED, 0xD3, 0xEC, 0x2A, 0xEF },
{ 0x04, 0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37, 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74, 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98, 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38,
0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C, 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7, 0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F, 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29,
0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C, 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0, 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D, 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F },
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF,
0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A, 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73 }
}
};
/*** Private Functions ************************************************************/
/*F********************************************************************************/
/*!
\Function _CryptNistPointCalculate
\Description
Does the math for doubling / adding points given they you have calculated
S over the curve's prime
\Input *pState - curve state
\Input *pP - point P
\Input *pQ - point Q
\Input *pS - S used in calculation
\Input *pResult - [out] point R that is calculation of P+Q
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
static void _CryptNistPointCalculate(CryptNistT *pState, CryptEccPointT *pP, CryptEccPointT *pQ, CryptBnT *pS, CryptEccPointT *pResult)
{
CryptEccPointT Result;
// X = S² - P.X - Q.X
CryptBnInitSet(&Result.X, 0);
CryptBnModMultiply(&Result.X, pS, pS, &pState->Prime);
CryptBnSubtract(&Result.X, &Result.X, &pP->X);
CryptBnSubtract(&Result.X, &Result.X, &pQ->X);
// Y = S * (P.X - X) - P.Y
CryptBnInitSet(&Result.Y, 0);
CryptBnSubtract(&Result.Y, &pP->X, &Result.X);
CryptBnModMultiply(&Result.Y, &Result.Y, pS, &pState->Prime);
CryptBnSubtract(&Result.Y, &Result.Y, &pP->Y);
// X %= Curve.Prime
CryptBnMod(&Result.X, &pState->Prime, &Result.X, NULL);
// Y %= Curve.Prime
CryptBnMod(&Result.Y, &pState->Prime, &Result.Y, NULL);
// write out the result
ds_memcpy(pResult, &Result, sizeof(*pResult));
}
/*F********************************************************************************/
/*!
\Function _CryptNistPointAddition
\Description
Adds two points together
\Input *pState - curve state
\Input *pPoint1 - first point in addition
\Input *pPoint2 - second point in addtion
\Input *pResult - [out] point R that is result of addition
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
static void _CryptNistPointAddition(CryptNistT *pState, CryptEccPointT *pPoint1, CryptEccPointT *pPoint2, CryptEccPointT *pResult)
{
if (memcmp(pPoint1, &_Zero, sizeof(*pPoint1)) == 0)
{
ds_memcpy(pResult, pPoint2, sizeof(*pResult));
}
else if (memcmp(pPoint2, &_Zero, sizeof(*pPoint2)) == 0)
{
ds_memcpy(pResult, pPoint1, sizeof(*pResult));
}
else
{
CryptBnT A, B;
CryptBnInitSet(&A, 0);
CryptBnInitSet(&B, 0);
// A = y1 - y2
CryptBnSubtract(&A, &pPoint1->Y, &pPoint2->Y);
// B = (x1 - x2)⁻¹ % Curve.Prime [Inverse Modulus]
CryptBnSubtract(&B, &pPoint1->X, &pPoint2->X);
CryptBnInverseMod(&B, &pState->Prime);
// A *= B
CryptBnModMultiply(&A, &A, &B, &pState->Prime);
// calculate the result
_CryptNistPointCalculate(pState, pPoint1, pPoint2, &A, pResult);
}
}
/*F********************************************************************************/
/*!
\Function _CryptNistPointDouble
\Description
Doubles a point
\Input *pState - curve state
\Input *pPoint - point that we are doubling
\Input *pResult - [out] result of the doubling
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
static void _CryptNistPointDouble(CryptNistT *pState, CryptEccPointT *pPoint, CryptEccPointT *pResult)
{
CryptBnT A, B;
// if attempting to double zero, return
if (memcmp(pPoint, &_Zero, sizeof(*pPoint)) == 0)
{
return;
}
// A = 3 * x * x + a
CryptBnInitSet(&A, 3);
CryptBnMultiply(&A, &A, &pPoint->X);
CryptBnMultiply(&A, &A, &pPoint->X);
CryptBnAccumulate(&A, &pState->CoefficientA);
// B = (2 * y)⁻¹ % Curve.Prime [Inverse Modulus]
CryptBnClone(&B, &pPoint->Y);
CryptBnLeftShift(&B);
CryptBnInverseMod(&B, &pState->Prime);
// A *= B
CryptBnModMultiply(&A, &A, &B, &pState->Prime);
// calculate the result
_CryptNistPointCalculate(pState, pPoint, pPoint, &A, pResult);
}
/*F********************************************************************************/
/*!
\Function _CryptNistDoubleAndAdd
\Description
Performs the scalar multiplication using double and add
operations using a sliding window for the private key
\Input *pState - curve state
\Input *pInput - point we are multiplying by the private key
\Input *pPrivateKey - private key was using to multiply
\Input *pResult - [out] result of the operation
\Output
int32_t - zero=success, otherwise=pending
\Notes
Adapted from the sliding window algorithm used for exponentiation
in RSA
See Handbook of Applied Cryptography Chapter 14.6.1 (14.85):
http://cacr.uwaterloo.ca/hac/about/chap14.pdf
\Version 05/08/2017 (eesponda)
*/
/********************************************************************************F*/
static int32_t _CryptNistDoubleAndAdd(CryptNistT *pState, CryptEccPointT *pInput, CryptBnT *pPrivateKey, CryptEccPointT *pResult)
{
int32_t iIter = CRYPTNIST_NUM_ITERATIONS;
do
{
const uint64_t uTick = NetTickUsec();
if (pState->iKeyIndex < 0)
{
int32_t iTableIndex;
CryptEccPointT Double;
// init working state
ds_memcpy(pResult, &_Zero, sizeof(*pResult));
// query memgroup
DirtyMemGroupQuery(&pState->iMemGroup, &pState->pMemGroupUserdata);
if ((pState->pTable = (CryptEccPointT *)DirtyMemAlloc(sizeof(*pState->pTable) * CRYPTNIST_TABLE_SIZE, CRYPTNIST_MEMID, pState->iMemGroup, pState->pMemGroupUserdata)) == NULL)
{
NetPrintf(("cryptnist: failed to allocate window table\n"));
return(0);
}
ds_memclr(pState->pTable, sizeof(*pState->pTable) * CRYPTNIST_TABLE_SIZE);
/* put the input into the first
index of the table, this will make it so further
entries in the table will not be too large
for our modulus operations */
ds_memcpy(&pState->pTable[0], pInput, sizeof(pState->pTable[0]));
// calculate doubled input as the basis for the other calculations
_CryptNistPointDouble(pState, pInput, &Double);
// calculate the further added points for the rest of the table
for (iTableIndex = 1; iTableIndex < CRYPTNIST_TABLE_SIZE; iTableIndex += 1)
{
_CryptNistPointAddition(pState, &pState->pTable[iTableIndex - 1], &Double, &pState->pTable[iTableIndex]);
}
// set the defaults
pState->iKeyIndex = CryptBnBitLen(pPrivateKey) - 1;
// clear the profiling information
pState->uCryptUSecs = 0;
}
/* scan backwards from the current private key bit
until a set bit is found to denote the start of the window
doubling the results until such a bit is found */
if (CryptBnBitTest(pPrivateKey, pState->iKeyIndex))
{
int32_t iWindowBit, iWindowValue, iWindowEnd;
/* scan backwards from the start of the window until the last set bit in the range of the window size is found which denotes the end bit index of the window
calculate the value of the window that will be used when adding against our precomputed table.
we skip the first bit as we know it is set based on the current private key bit check we make right above */
for (iWindowBit = 1, iWindowValue = 1, iWindowEnd = 0; (iWindowBit < CRYPTNIST_WINDOW_SIZE) && ((pState->iKeyIndex - iWindowBit) >= 0); iWindowBit += 1)
{
if (CryptBnBitTest(pPrivateKey, pState->iKeyIndex - iWindowBit))
{
iWindowValue <<= (iWindowBit - iWindowEnd);
iWindowValue |= 1; // force odd
iWindowEnd = iWindowBit;
}
}
// double for each bits in the window and moving the private key bit index down each time
for (iWindowBit = 0; iWindowBit < iWindowEnd + 1; iWindowBit += 1, pState->iKeyIndex -= 1)
{
_CryptNistPointDouble(pState, pResult, pResult);
}
// use the window value to get which precomputed power we need to multiply
_CryptNistPointAddition(pState, pResult, &pState->pTable[iWindowValue/2], pResult);
}
else
{
_CryptNistPointDouble(pState, pResult, pResult);
pState->iKeyIndex -= 1;
}
// update profiler information
pState->uCryptUSecs += NetTickDiff(NetTickUsec(), uTick);
}
while ((pState->iKeyIndex >= 0) && (--iIter > 0));
// cleanup memory when done
if (pState->iKeyIndex < 0)
{
DirtyMemFree(pState->pTable, CRYPTNIST_MEMID, pState->iMemGroup, pState->pMemGroupUserdata);
pState->pTable = NULL;
}
return(pState->iKeyIndex >= 0);
}
/*F********************************************************************************/
/*!
\Function _CryptNistDoubleMultiply
\Description
Uses Shamir's Trick to do double point multiplication:
(pInput1 * pPrivateKey1) + (pInput2 * pPrivateKey2)
\Input *pState - curve state
\Input *pInput1 - point we are multiplying by pMul1
\Input *pMul1 - big number we are multiplying by pInput1
\Input *pInput2 - point we are multiplying by pMul2
\Input *pMul2 - big number we are multiplying by pInput2
\Input *pResult - [out] result of the operation
\Output
int32_t - zero=success, otherwise=pending
\Notes
For more information on the algorithm, see the Double-scalar
multiplication slides: http://www.lirmm.fr/~imbert/talks/laurent_Asilomar_08.pdf
\Version 05/15/2017 (eesponda)
*/
/********************************************************************************F*/
static int32_t _CryptNistDoubleMultiply(CryptNistT *pState, CryptEccPointT *pInput1, CryptBnT *pMul1, CryptEccPointT *pInput2, CryptBnT *pMul2, CryptEccPointT *pResult)
{
int32_t iIter = CRYPTNIST_NUM_ITERATIONS;
do
{
const uint64_t uTick = NetTickUsec();
uint8_t bLHBitSet, bRHBitSet;
if (pState->iKeyIndex < 0)
{
DirtyMemGroupQuery(&pState->iMemGroup, &pState->pMemGroupUserdata);
if ((pState->pTable = (CryptEccPointT *)DirtyMemAlloc(sizeof(*pState->pTable), CRYPTNIST_MEMID, pState->iMemGroup, pState->pMemGroupUserdata)) == NULL)
{
NetPrintf(("cryptnist: failed to allocate window table\n"));
return(0);
}
ds_memclr(pState->pTable, sizeof(*pState->pTable));
// precompute pInput1 + pInput2
_CryptNistPointAddition(pState, pInput1, pInput2, &pState->pTable[0]);
// init working state
ds_memcpy(pResult, &_Zero, sizeof(*pResult));
// set the maximum bit size
pState->iKeyIndex = DS_MAX(CryptBnBitLen(pMul1), CryptBnBitLen(pMul2)) - 1;
// clear the profiling information
pState->uCryptUSecs = 0;
}
// double for each column
_CryptNistPointDouble(pState, pResult, pResult);
// check the bit set in the column
bLHBitSet = CryptBnBitTest(pMul1, pState->iKeyIndex);
bRHBitSet = CryptBnBitTest(pMul2, pState->iKeyIndex);
// if only left hand, then add by that side
if ((bLHBitSet == TRUE) && (bRHBitSet == FALSE))
{
_CryptNistPointAddition(pState, pResult, pInput1, pResult);
}
// if only right hand, then add by that side
else if ((bLHBitSet == FALSE) && (bRHBitSet == TRUE))
{
_CryptNistPointAddition(pState, pResult, pInput2, pResult);
}
// if both sides, then add by the sum of both
else if ((bLHBitSet == TRUE) && (bRHBitSet == TRUE))
{
_CryptNistPointAddition(pState, pResult, &pState->pTable[0], pResult);
}
// update profiler information
pState->uCryptUSecs += NetTickDiff(NetTickUsec(), uTick);
}
while ((--pState->iKeyIndex >= 0) && (--iIter > 0));
// cleanup table memory
if (pState->iKeyIndex < 0)
{
DirtyMemFree(pState->pTable, CRYPTNIST_MEMID, pState->iMemGroup, pState->pMemGroupUserdata);
pState->pTable = NULL;
}
return(pState->iKeyIndex >= 0);
}
/*F********************************************************************************/
/*!
\Function _CryptNistRand
\Description
Generates a random number in the range [1, curve.order-1]
\Input *pState - curve state
\Input *pResult - [out] random number generated
\Version 05/15/2017 (eesponda)
*/
/********************************************************************************F*/
static void _CryptNistRand(const CryptNistT *pState, CryptBnT *pResult)
{
uint8_t aSecret[48];
CryptBnT Temp;
// generate a secret to the specified size
CryptRandGet(aSecret, sizeof(aSecret));
CryptBnInitFrom(pResult, -1, aSecret, DS_MIN((int32_t)sizeof(aSecret), pState->iSize));
// clone order so we don't modify
CryptBnClone(&Temp, &pState->Order);
// random number = (rand() % order-1 + 1)
CryptBnDecrement(&Temp);
CryptBnMod(pResult, &Temp, pResult, NULL);
CryptBnIncrement(pResult);
}
/*F********************************************************************************/
/*!
\Function _CryptNistDsaInitHash
\Description
Initializes the hash, truncating if necessary
\Input *pState - curve state
\Input *pHashData - the buffer with the hash digest
\Input iHashSize - size of the buffer
\Input *pHash - [out] big number representation of the hash
\Notes
Based on the algorithm for ECDSA, the hash needs to be truncated to the
bit length of curve.order which can be done by a right shift. The problem
is that other implementations don't follow this and just truncate whole
bytes first. For this reason that is what we do here.
\Version 02/13/2018 (eesponda)
*/
/********************************************************************************F*/
static void _CryptNistDsaInitHash(const CryptNistT *pState, const uint8_t *pHashData, int32_t iHashSize, CryptBnT *pHash)
{
int32_t iOrderSize = CryptBnBitLen(&pState->Order);
if ((iHashSize*8) > iOrderSize)
{
iHashSize = (iOrderSize + 7) / 8;
}
CryptBnInitFrom(pHash, -1, pHashData, iHashSize);
}
/*F********************************************************************************/
/*!
\Function _CryptNistInit
\Description
Initializes the curve state
\Input *pState - curve state
\Input *pCurve - curve data to initialize the state
\Output
int32_t - 0=success, negative=failure
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
static int32_t _CryptNistInit(CryptNistT *pState, const NistCurveT *pCurve)
{
// initialize the curve parameters
pState->iSize = pCurve->iSize;
CryptBnInitFrom(&pState->Prime, -1, pCurve->aPrime, pCurve->iSize);
CryptBnInitFrom(&pState->CoefficientA, -1, pCurve->aCoefficientA, pCurve->iSize);
CryptBnInitFrom(&pState->CoefficientB, -1, pCurve->aCoefficientB, pCurve->iSize);
CryptBnInitFrom(&pState->Order, -1, pCurve->aOrder, pCurve->iSize);
if (CryptNistPointInitFrom(&pState->BasePoint, pCurve->aBasePoint, pCurve->iSize * 2 + 1) < 0)
{
return(-1);
}
// initialize the computation state
pState->iKeyIndex = -1;
return(0);
}
/*F********************************************************************************/
/*!
\Function _CryptNistDsaInitK
\Description
Generates a K value for signing operations deterministically based on
the private key, message hash and curve
\Input *pState - curve state
\Input *pPrivateKey - private key information
\Input *pHash - hash of the message we are signing
\Input iHashSize - size of the hash
\Input *pResult - [out] K value for signing
\Notes
This work is based on RFC6979: https://tools.ietf.org/html/rfc6979
It allows for selecting a K value without the use of random data.
Aside from this generating benefit the ability to generate deterministic
signatures are required for other cryptographic operations.
\Version 01/08/2018 (eesponda)
*/
/********************************************************************************F*/
static void _CryptNistDsaInitK(const CryptNistT *pState, const CryptBnT *pPrivateKey, const uint8_t *pHash, int32_t iHashSize, CryptBnT *pResult)
{
static const uint8_t _aZero[] = { 0x00 }, _aOne[] = { 0x01 };
CryptHashTypeE eHashType;
uint8_t aNewHash[CRYPTHASH_MAXDIGEST], aK[CRYPTHASH_MAXDIGEST], aV[CRYPTHASH_MAXDIGEST], aT[CRYPTHASH_MAXDIGEST], aPrivateKey[48];
CryptHmacMsgT aMessages[4];
int32_t iHashOffset = iHashSize < pState->iSize ? pState->iSize - iHashSize : 0;
// figure out the hash based on the type
if ((eHashType = CryptHashGetBySize(iHashSize)) == CRYPTHASH_NULL)
{
NetPrintf(("cryptnist: cannot generate deterministic k, unsupported hash used (size=%d)\n", iHashSize));
return;
}
// copy the hash to our buffer, this ensures correct format
ds_memclr(aNewHash, sizeof(aNewHash));
ds_memcpy_s(aNewHash+iHashOffset, sizeof(aNewHash)-iHashOffset, pHash, iHashSize);
// init k, v, private key
ds_memclr(aK, sizeof(aK));
ds_memset(aV, 0x01, sizeof(aV));
CryptBnFinal(pPrivateKey, aPrivateKey, pState->iSize);
// K = HMAC(V || 0x00 || PrivateKey || H(m))
aMessages[0].pMessage = aV;
aMessages[0].iMessageLen = iHashSize;
aMessages[1].pMessage = _aZero;
aMessages[1].iMessageLen = sizeof(_aZero);
aMessages[2].pMessage = aPrivateKey;
aMessages[2].iMessageLen = pState->iSize;
aMessages[3].pMessage = aNewHash;
aMessages[3].iMessageLen = pState->iSize;
CryptHmacCalcMulti(aK, iHashSize, aMessages, 4, aK, iHashSize, eHashType);
// V = HMAC(V)
CryptHmacCalc(aV, iHashSize, aV, iHashSize, aK, iHashSize, eHashType);
// K = HMAC(V || 0x01 || PrivateKey || H(m))
aMessages[0].pMessage = aV;
aMessages[0].iMessageLen = iHashSize;
aMessages[1].pMessage = _aOne;
aMessages[1].iMessageLen = sizeof(_aOne);
aMessages[2].pMessage = aPrivateKey;
aMessages[2].iMessageLen = pState->iSize;
aMessages[3].pMessage = aNewHash;
aMessages[3].iMessageLen = pState->iSize;
CryptHmacCalcMulti(aK, iHashSize, aMessages, 4, aK, iHashSize, eHashType);
// V = HMAC(V)
CryptHmacCalc(aV, iHashSize, aV, iHashSize, aK, iHashSize, eHashType);
// generate T for new K candidate
while (1)
{
int32_t iOffset = 0, iBytes;
while (iOffset < pState->iSize)
{
// V = HMAC(V)
CryptHmacCalc(aV, iHashSize, aV, iHashSize, aK, iHashSize, eHashType);
iBytes = DS_MIN(iHashSize, pState->iSize - iOffset);
ds_memcpy(aT+iOffset, aV, iBytes);
iOffset += iBytes;
}
// copy result and check if suitable
CryptBnInitFrom(pResult, -1, aT, pState->iSize);
if (CryptBnCompare(pResult, &pState->Order) < 0)
{
break;
}
// K = HMAC(V || 0x00)
aMessages[0].pMessage = aV;
aMessages[0].iMessageLen = iHashSize;
aMessages[1].pMessage = _aZero;
aMessages[1].iMessageLen = sizeof(_aZero);
CryptHmacCalcMulti(aK, iHashSize, aMessages, 2, aK, iHashSize, eHashType);
// V = HMAC(V)
CryptHmacCalc(aV, iHashSize, aV, iHashSize, aK, iHashSize, eHashType);
}
}
/*** Public Functions *************************************************************/
/*F********************************************************************************/
/*!
\Function CryptNistInitDh
\Description
Initializes the curve state for performing diffie hellmen key exchange
\Input *pState - curve state
\Input iCurveType - identifier for the curve we are using
\Output
int32_t - 0=success, negative=failure
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
int32_t CryptNistInitDh(CryptNistDhT *pState, int32_t iCurveType)
{
int32_t iResult, iCurveIdx;
const NistCurveT *pCurve = NULL;
ds_memclr(pState, sizeof(*pState));
// find the curve
for (iCurveIdx = 0; iCurveIdx < (signed)(sizeof(_aEccCurves)/sizeof(_aEccCurves[0])); iCurveIdx += 1)
{
if (iCurveType == _aEccCurves[iCurveIdx].iIdent)
{
pCurve = &_aEccCurves[iCurveIdx];
break;
}
}
// check to make sure we found a valid curve
if (pCurve == NULL)
{
NetPrintf(("cryptnist: could not find matching curve information for dh given ident %d\n", iCurveType));
return(-1);
}
// initialize the curve and computation data
if ((iResult = _CryptNistInit(&pState->Ecc, pCurve)) == 0)
{
// generate random private key
_CryptNistRand(&pState->Ecc, &pState->PrivateKey);
}
return(iResult);
}
/*F********************************************************************************/
/*!
\Function CryptNistInitDsa
\Description
Initializes the curve state for performing dsa sign and verify
\Input *pState - curve state
\Input iCurveType - identifier for the curve we are using
\Output
int32_t - 0=success, negative=failure
\Version 04/12/2018 (eesponda)
*/
/********************************************************************************F*/
int32_t CryptNistInitDsa(CryptNistDsaT *pState, int32_t iCurveType)
{
int32_t iCurveIdx;
const NistCurveT *pCurve = NULL;
ds_memclr(pState, sizeof(*pState));
// find the curve
for (iCurveIdx = 0; iCurveIdx < (signed)(sizeof(_aEccCurves)/sizeof(_aEccCurves[0])); iCurveIdx += 1)
{
if (iCurveType == _aEccCurves[iCurveIdx].iIdent)
{
pCurve = &_aEccCurves[iCurveIdx];
break;
}
}
// check to make sure we found a valid curve
if (pCurve == NULL)
{
NetPrintf(("cryptnist: could not find matching curve information for dh given ident %d\n", iCurveType));
return(-1);
}
// initialize the curve and computation data
return(_CryptNistInit(&pState->Ecc, pCurve));
}
/*F********************************************************************************/
/*!
\Function CryptNistPublic
\Description
Generates a public key
\Input *pState - curve state
\Input *pResult - [out] point on the curve that represents the public key (optional)
\Input *pCryptUsecs - [out] timing information for the operation (optional)
\Output
int32_t - zero=success, otherwise=pending
\Notes
If pResult is NULL, the result can be taken from CryptNistT.Result
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
int32_t CryptNistPublic(CryptNistDhT *pState, CryptEccPointT *pResult, uint32_t *pCryptUsecs)
{
int32_t iResult;
if ((iResult = _CryptNistDoubleAndAdd(&pState->Ecc, &pState->Ecc.BasePoint, &pState->PrivateKey, &pState->Result)) == 0)
{
// copy the timing if passed in
if (pCryptUsecs != NULL)
{
*pCryptUsecs = pState->Ecc.uCryptUSecs;
}
/* copy the final result if a valid destination is provided (the caller can copy the result
later if desired) */
if (pResult != NULL)
{
ds_memcpy(pResult, &pState->Result, sizeof(*pResult));
}
}
return(iResult);
}
/*F********************************************************************************/
/*!
\Function CryptNistSecret
\Description
Generates a shared secret
\Input *pState - curve state
\Input *pPublicKey - point as a basis to compute the shared secret
\Input *pResult - [out] point on the curve that represents the shared secret (optional)
\Input *pCryptUsecs - [out] timing information for the operation (optional)
\Output
int32_t - zero=success, otherwise=pending
\Notes
If pResult is NULL, the result can be taken from CryptNistT.Result
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
int32_t CryptNistSecret(CryptNistDhT *pState, CryptEccPointT *pPublicKey, CryptEccPointT *pResult, uint32_t *pCryptUsecs)
{
int32_t iResult;
if ((iResult = _CryptNistDoubleAndAdd(&pState->Ecc, pPublicKey, &pState->PrivateKey, &pState->Result)) == 0)
{
// copy the timing if passed in
if (pCryptUsecs != NULL)
{
*pCryptUsecs = pState->Ecc.uCryptUSecs;
}
/* copy the final result if a valid destination is provided (the caller can copy the result
later if desired) */
if (pResult != NULL)
{
ds_memcpy(pResult, &pState->Result, sizeof(*pResult));
}
}
return(iResult);
}
/*F********************************************************************************/
/*!
\Function CryptNistSign
\Description
Generates an elliptic curve dsa signature
\Input *pState - curve state
\Input *pPrivateKey - key used to sign
\Input *pHash - hash we are creating a signature for
\Input iHashSize - size of the hash
\Input *pSignature - [out] point on the curve that represents our signature (optional)
\Input *pCryptUsecs - [out] timing information for the operation (optional)
\Output
int32_t - zero=success, otherwise=pending
\Notes
If pSignature is NULL, the result can be taken from CryptNistT.Result
\Version 05/15/2017 (eesponda)
*/
/********************************************************************************F*/
int32_t CryptNistSign(CryptNistDsaT *pState, const CryptBnT *pPrivateKey, const uint8_t *pHash, int32_t iHashSize, CryptEccPointT *pSignature, uint32_t *pCryptUsecs)
{
int32_t iResult;
CryptNistT *pEcc = &pState->Ecc;
// generate a secret to the specified size
if (CryptBnBitLen(&pState->K) == 0)
{
_CryptNistDsaInitK(pEcc, pPrivateKey, pHash, iHashSize, &pState->K);
}
// calculate x,y using k * basepoint
if ((iResult = _CryptNistDoubleAndAdd(pEcc, &pEcc->BasePoint, &pState->K, &pState->Result)) == 0)
{
const uint64_t uTickUsecs = NetTickUsec();
CryptBnT Hash;
// r = x % order
CryptBnMod(&pState->Result.X, &pEcc->Order, &pState->Result.X, NULL);
// s1 = ((hash + r * private)
CryptBnInitFrom(&Hash, -1, pHash, iHashSize);
// truncate the hash if necessary
_CryptNistDsaInitHash(pEcc, pHash, iHashSize, &Hash);
CryptBnMultiply(&pState->Result.Y, &pState->Result.X, pPrivateKey);
CryptBnAccumulate(&pState->Result.Y, &Hash);
// s2 = inverse_mod(k, order)
CryptBnInverseMod(&pState->K, &pEcc->Order);
// s = s1 * s2 % order
CryptBnModMultiply(&pState->Result.Y, &pState->Result.Y, &pState->K, &pEcc->Order);
/* copy the final result if a valid destination is provided (the caller can copy the result
later if desired) */
if (pSignature != NULL)
{
ds_memcpy(pSignature, &pState->Result, sizeof(*pSignature));
}
// update profiler information
pEcc->uCryptUSecs += NetTickDiff(NetTickUsec(), uTickUsecs);
// copy the timing if passed in
if (pCryptUsecs != NULL)
{
*pCryptUsecs = pEcc->uCryptUSecs;
}
}
return(iResult);
}
/*F********************************************************************************/
/*!
\Function CryptNistVerify
\Description
Verify the elliptic curve dsa signature
\Input *pState - curve state
\Input *pPublicKey - the public used for verifying the signature
\Input *pHash - hash we are verifying a signature for
\Input iHashSize - size of the hash
\Input *pSignature - point on the curve that represents our signature
\Input *pCryptUsecs - [out] timing information for the operation (optional)
\Output
int32_t - zero=success, >0=pending, <0=failure
\Version 05/15/2017 (eesponda)
*/
/********************************************************************************F*/
int32_t CryptNistVerify(CryptNistDsaT *pState, CryptEccPointT *pPublicKey, const uint8_t *pHash, int32_t iHashSize, CryptEccPointT *pSignature, uint32_t *pCryptUsecs)
{
int32_t iResult;
CryptNistT *pEcc = &pState->Ecc;
// initialize state if necessary
if (pEcc->iKeyIndex < 0)
{
CryptBnT S, Hash;
// truncate the hash if necessary
_CryptNistDsaInitHash(pEcc, pHash, iHashSize, &Hash);
// s' = inverse_mod(signature.s, order);
CryptBnClone(&S, &pSignature->Y);
CryptBnInverseMod(&S, &pEcc->Order);
// u1 = s' * hash % order
CryptBnModMultiply(&pState->U1, &Hash, &S, &pEcc->Order);
// u2 = s' * signature.r % order
CryptBnModMultiply(&pState->U2, &pSignature->X, &S, &pEcc->Order);
}
// result = (u1 * basepoint) + (u2 * publickey)
if ((iResult = _CryptNistDoubleMultiply(pEcc, &pEcc->BasePoint, &pState->U1, pPublicKey, &pState->U2, &pState->Result)) == 0)
{
// copy the timing if passed in
if (pCryptUsecs != NULL)
{
*pCryptUsecs = pEcc->uCryptUSecs;
}
// signature valid if result.x == signature.x
iResult = (memcmp(&pSignature->X, &pState->Result.X, sizeof(pSignature->X)) == 0) ? 0 : -1;
}
return(iResult);
}
/*F********************************************************************************/
/*!
\Function CryptNistPointValidate
\Description
Validate that the point is on the curve
\Input *pState - curve state
\Input *pPoint - point we are validating
\Output
uint8_t - TRUE=on the curve, FALSE=not on the curve
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
uint8_t CryptNistPointValidate(const CryptNistDhT *pState, const CryptEccPointT *pPoint)
{
CryptBnT Result, X, Y, A;
const CryptNistT *pEcc = &pState->Ecc;
// a' = curve.a * x
CryptBnMultiply(&A, &pEcc->CoefficientA, &pPoint->X);
// y' = y * y
CryptBnMultiply(&Y, &pPoint->Y, &pPoint->Y);
// x' = x * x * x
CryptBnMultiply(&X, &pPoint->X, &pPoint->X);
CryptBnMultiply(&X, &X, &pPoint->X);
// result = y' - x' - a' - curve.b
CryptBnSubtract(&Result, &Y, &X);
CryptBnSubtract(&Result, &Result, &A);
CryptBnSubtract(&Result, &Result, &pEcc->CoefficientB);
CryptBnMod(&Result, &pEcc->Prime, &Result, NULL);
return(CryptBnBitLen(&Result) == 0);
}
/*F********************************************************************************/
/*!
\Function CryptNistPointInitFrom
\Description
Loads a point from a buffer
\Input *pPoint - [out] the point we are writing the information to
\Input *pBuffer - buffer we are reading the point data from
\Input iBufLen - size of the buffer
\Output
int32_t - 0=success, negative=failure
\Notes
We only support reading uncompressed points
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
int32_t CryptNistPointInitFrom(CryptEccPointT *pPoint, const uint8_t *pBuffer, int32_t iBufLen)
{
int32_t iPointLen = (iBufLen-1)/2;
// we only support uncompressed points, so ensure that this is the case
if (*pBuffer++ != CRYPTNIST_UNCOMPRESSED_MARKER)
{
NetPrintf(("cryptnist: could not initialize curve, base point in wrong format\n"));
return(-1);
}
CryptBnInitFrom(&pPoint->X, -1, pBuffer, iPointLen);
CryptBnInitFrom(&pPoint->Y, -1, pBuffer+iPointLen, iPointLen);
return(0);
}
/*F********************************************************************************/
/*!
\Function CryptNistPointFinal
\Description
Loads a point into a buffer for dh
\Input *pState - curve state
\Input *pPoint - point state or NULL to use curve result
\Input bSecret - is this the shared secret? (only need x)
\Input *pBuffer - buffer we are writing the point data to
\Input iBufLen - size of the buffer
\Output
int32_t - 0=success, negative=failure
\Notes
We only support writing uncompressed points
\Version 02/17/2017 (eesponda)
*/
/********************************************************************************F*/
int32_t CryptNistPointFinal(const CryptNistDhT *pState, const CryptEccPointT *pPoint, uint8_t bSecret, uint8_t *pBuffer, int32_t iBufLen)
{
if ((pState != NULL) && (pPoint == NULL))
{
pPoint = &pState->Result;
}
const int32_t iXSize = CryptBnByteLen(&pPoint->X);
const int32_t iYSize = CryptBnByteLen(&pPoint->Y);
const int32_t iOutputSize = !bSecret ? (iXSize + iYSize + 1) : iXSize;
if (iBufLen < iOutputSize)
{
NetPrintf(("cryptnist: not enough space in output buffer to encode points\n"));
return(-1);
}
if (!bSecret)
{
*pBuffer++ = CRYPTNIST_UNCOMPRESSED_MARKER;
CryptBnFinal(&pPoint->X, pBuffer, iXSize);
CryptBnFinal(&pPoint->Y, pBuffer+iXSize, iYSize);
}
else
{
CryptBnFinal(&pPoint->X, pBuffer, iXSize);
}
return(iOutputSize);
}