/*
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
/* $Id: prng_fips1861.c,v 1.27 2006/10/13 16:54:04 wtchang%redhat.com Exp $ */

#include "prerr.h"
#include "secerr.h"

#include "prtypes.h"
#include "prinit.h"
#include "blapi.h"
#include "nssilock.h"
#include "secitem.h"
#include "sha_fast.h"
#include "sha256.h"
#include "secrng.h"	/* for RNG_GetNoise() */
#include "secmpi.h"

/*
 * The minimum amount of seed data required before the generator will
 * provide data.
 * Note that this is a measure of the number of bytes sent to
 * RNG_RandomUpdate, not the actual amount of entropy present in the
 * generator.  Naturally, it is impossible to know (at this level) just
 * how much entropy is present in the provided seed data.  A bare minimum
 * of entropy would be 20 bytes, so by requiring 1K this code is making
 * the tacit assumption that at least 1 byte of pure entropy is provided
 * with every 8 bytes supplied to RNG_RandomUpdate.  The reality of this
 * assumption is left up to the caller.
 */
#define MIN_SEED_COUNT 1024

/*
 * Steps taken from Algorithm 1 of FIPS 186-2 Change Notice 1
 */

/*
 * According to FIPS 186-2, 160 <= b <= 512.
 * For our purposes, we will assume b == 160,
 * 256, or 512 (the output size of SHA-1,
 * SHA-256, or SHA-512).
 */
#define FIPS_B     256
#define BSIZE      (FIPS_B / PR_BITS_PER_BYTE)
#if BSIZE != SHA256_LENGTH
#error "this file requires that BSIZE and SHA256_LENGTH be equal"
#endif

/* Output size of the G function */
#define FIPS_G     160
#define GSIZE      (FIPS_G / PR_BITS_PER_BYTE)

/*
 * Add two b-bit numbers represented as arrays of BSIZE bytes.
 * The numbers are big-endian, MSB first, so addition is done
 * from the end of the buffer to the beginning.
 */
#define ADD_B_BIT_PLUS_CARRY(dest, add1, add2, cy) \
    carry = cy; \
    for (k=BSIZE-1; k>=0; --k) { \
	carry += add1[k] + add2[k]; \
	dest[k] = (PRUint8)carry; \
	carry >>= 8; \
    }

#define ADD_B_BIT_2(dest, add1, add2) \
	ADD_B_BIT_PLUS_CARRY(dest, add1, add2, 0)


/*
 * FIPS requires result from Step 3.3 to be reduced mod q when generating
 * random numbers for DSA.
 *
 * Input: w, 2*GSIZE bytes
 *        q, DSA_SUBPRIME_LEN bytes
 * Output: xj, DSA_SUBPRIME_LEN bytes
 */
SECStatus
FIPS186Change_ReduceModQForDSA(const unsigned char *w,
                               const unsigned char *q,
                               unsigned char *xj)
{
    mp_int W, Q, Xj;
    mp_err err;
    SECStatus rv = SECSuccess;

    /* Initialize MPI integers. */
    MP_DIGITS(&W) = 0;
    MP_DIGITS(&Q) = 0;
    MP_DIGITS(&Xj) = 0;
    CHECK_MPI_OK( mp_init(&W) );
    CHECK_MPI_OK( mp_init(&Q) );
    CHECK_MPI_OK( mp_init(&Xj) );
    /*
     * Convert input arguments into MPI integers.
     */
    CHECK_MPI_OK( mp_read_unsigned_octets(&W, w, 2*GSIZE) );
    CHECK_MPI_OK( mp_read_unsigned_octets(&Q, q, DSA_SUBPRIME_LEN) );
    /*
     * Algorithm 1 of FIPS 186-2 Change Notice 1, Step 3.3
     *
     * xj = (w0 || w1) mod q
     */
    CHECK_MPI_OK( mp_mod(&W, &Q, &Xj) );
    CHECK_MPI_OK( mp_to_fixlen_octets(&Xj, xj, DSA_SUBPRIME_LEN) );
cleanup:
    mp_clear(&W);
    mp_clear(&Q);
    mp_clear(&Xj);
    if (err) {
	MP_TO_SEC_ERROR(err);
	rv = SECFailure;
    }
    return rv;
}

/*
 * Specialized SHA1-like function.  This function appends zeroes to a 
 * single input block and runs a single instance of the compression function, 
 * as specified in FIPS 186-2 appendix 3.3.
 */
static void 
RNG_UpdateAndEnd_FIPS186_2(SHA1Context *ctx, 
                           unsigned char *input, unsigned int inputLen,
                           unsigned char *hashout, unsigned int *pDigestLen, 
                           unsigned int maxDigestLen);

/*
 * Global RNG context
 */ 
struct RNGContextStr {
    PRUint8   XKEY[BSIZE]; /* Seed for next SHA iteration */
    PRUint8   Xj[2*GSIZE]; /* Output from previous operation. */
    PZLock   *lock;        /* Lock to serialize access to global rng */
    PRUint8   avail;       /* # bytes of output available, [0...2*GSIZE] */
    PRUint32  seedCount;   /* number of seed bytes given to generator */
    PRBool    isValid;     /* false if RNG reaches an invalid state */
};
typedef struct RNGContextStr RNGContext;
static RNGContext *globalrng = NULL;
static RNGContext theGlobalRng;

/*
 * Clean up the global RNG context
 */
static void
freeRNGContext()
{
    unsigned char inputhash[BSIZE];
    SECStatus rv;

    /* destroy context lock */
    PZ_DestroyLock(globalrng->lock);

    /* zero global RNG context except for XKEY to preserve entropy */
    rv = SHA256_HashBuf(inputhash, globalrng->XKEY, BSIZE);
    PORT_Assert(SECSuccess == rv);
    memset(globalrng, 0, sizeof(*globalrng));
    memcpy(globalrng->XKEY, inputhash, BSIZE);

    globalrng = NULL;
}

/*
 * The core of Algorithm 1 of FIPS 186-2 Change Notice 1,
 * separated from alg_fips186_2_cn_1 as a standalone function
 * for FIPS algorithm testing.
 *
 * Parameters:
 *   XKEY [input/output]: the state of the RNG (seed-key)
 *   XSEEDj [input]: optional user input (seed)
 *   x_j [output]: output of the RNG
 *
 * Return value:
 * This function usually returns SECSuccess.  The only reason
 * this function returns SECFailure is that XSEEDj equals
 * XKEY, including the intermediate XKEY value between the two
 * iterations.  (This test is actually a FIPS 140-2 requirement
 * and not required for FIPS algorithm testing, but it is too
 * hard to separate from this function.)  If this function fails,
 * XKEY is not updated, but some data may have been written to
 * x_j, which should be ignored.
 */
SECStatus
FIPS186Change_GenerateX(unsigned char *XKEY, const unsigned char *XSEEDj,
                        unsigned char *x_j)
{
    /* SHA1 context for G(t, XVAL) function */
    SHA1Context sha1cx;
    /* XKEY for iteration 1 */
    PRUint8 XKEY_1[BSIZE];
    const PRUint8 *XKEY_old;
    PRUint8 *XKEY_new;
    /* input to hash function */
    PRUint8 XVAL[BSIZE];
    /* used by ADD_B_BIT macros */
    int k, carry;
    /* store the output of G(t, XVAL) in the rightmost GSIZE bytes */
    PRUint8 w_i[BSIZE];
    int i;
    unsigned int len;
    SECStatus rv = SECSuccess;

#if GSIZE < BSIZE
    /* zero the leftmost bytes so we can pass it to ADD_B_BIT_PLUS_CARRY */
    memset(w_i, 0, BSIZE - GSIZE);
#endif
    /* 
     * <Step 2> Initialize t, taken care of in SHA-1 (same initial values)
     *
     * <Step 3.1> XSEEDj is optional user input
     */ 
    for (i = 0; i < 2; i++) {
	/* only update XKEY when both iterations have been completed */
	if (i == 0) {
	    /* for iteration 0 */
	    XKEY_old = XKEY;
	    XKEY_new = XKEY_1;
	} else {
	    /* for iteration 1 */
	    XKEY_old = XKEY_1;
	    XKEY_new = XKEY;
	}
	/* 
	 * <Step 3.2a> XVAL = (XKEY + XSEEDj) mod 2^b
	 *     :always reduced mod 2^b, since storing as b-bit value
	 */
	if (XSEEDj) {
	    /* XSEEDj > 0 */
	    if (memcmp(XKEY_old, XSEEDj, BSIZE) == 0) {
		/* Should we add the error code SEC_ERROR_BAD_RNG_SEED? */
		PORT_SetError(SEC_ERROR_INVALID_ARGS);
		rv = SECFailure;
		goto done;
	    }
	    ADD_B_BIT_2(XVAL, XKEY_old, XSEEDj);
	} else {
	    /* XSEEDj == 0 */
	    memcpy(XVAL, XKEY_old, BSIZE);
	}
	/* 
	 * <Step 3.2b> Wi = G(t, XVAL)
	 *     :FIPS 186-2 specifies a different padding than the SHA1 180-1
	 *     :specification, this function is implemented in
	 *     :RNG_UpdateAndEnd_FIPS186_2 below.
	 */ 
	SHA1_Begin(&sha1cx);
	RNG_UpdateAndEnd_FIPS186_2(&sha1cx, XVAL, BSIZE,
				   &w_i[BSIZE - GSIZE], &len, GSIZE);
	/* 
	 * <Step 3.2c> XKEY = (1 + XKEY + Wi) mod 2^b
	 *     :always reduced mod 2^b, since storing as 160-bit value 
	 */
	ADD_B_BIT_PLUS_CARRY(XKEY_new, XKEY_old, w_i, 1);
	/*
	 * <Step 3.3> Xj = (W0 || W1)
	 */
	memcpy(&x_j[i*GSIZE], &w_i[BSIZE - GSIZE], GSIZE);
    }

done:
    /* housekeeping */
    memset(&w_i[BSIZE - GSIZE], 0, GSIZE);
    memset(XVAL, 0, BSIZE);
    memset(XKEY_1, 0, BSIZE);
    return rv;
}

/*
 * Implementation of Algorithm 1 of FIPS 186-2 Change Notice 1,
 * hereinafter called alg_cn_1().  It is assumed a lock for the global
 * rng context has already been acquired.
 * Calling this function with XSEEDj == NULL is equivalent to saying there
 * is no optional user input, which is further equivalent to saying that
 * the optional user input is 0.
 */
static SECStatus
alg_fips186_2_cn_1(RNGContext *rng, const unsigned char *XSEEDj)
{
    /* store a copy of the output to compare with the previous output */
    PRUint8 x_j[2*GSIZE];
    SECStatus rv;

    if (!rng->isValid) {
	/* RNG has alread entered an invalid state. */
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	return SECFailure;
    }
    rv = FIPS186Change_GenerateX(rng->XKEY, XSEEDj, x_j);
    if (rv != SECSuccess) {
	goto done;
    }
    /*     [FIPS 140-2] verify output does not match previous output */
    if (memcmp(x_j, rng->Xj, 2*GSIZE) == 0) {
	/* failed FIPS 140-2 continuous RNG test.  RNG now invalid. */
	rng->isValid = PR_FALSE;
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	rv = SECFailure;
	goto done;
    }
    /* Xj is the output */
    memcpy(rng->Xj, x_j, 2*GSIZE);
    /* Always have a full buffer after executing alg_cn_1() */
    rng->avail = 2*GSIZE;

done:
    /* housekeeping */
    memset(x_j, 0, 2*GSIZE);
    return rv;
}

/* Use NSPR to prevent RNG_RNGInit from being called from separate
 * threads, creating a race condition.
 */
static const PRCallOnceType pristineCallOnce;
static PRCallOnceType coRNGInit;
static PRStatus rng_init(void)
{
    unsigned char bytes[SYSTEM_RNG_SEED_COUNT];
    unsigned int numBytes;
    if (globalrng == NULL) {
	/* create a new global RNG context */
	globalrng = &theGlobalRng;
        PORT_Assert(NULL == globalrng->lock);
	/* create a lock for it */
	globalrng->lock = PZ_NewLock(nssILockOther);
	if (globalrng->lock == NULL) {
	    globalrng = NULL;
	    PORT_SetError(PR_OUT_OF_MEMORY_ERROR);
	    return PR_FAILURE;
	}
	/* the RNG is in a valid state */
	globalrng->isValid = PR_TRUE;
	/* Try to get some seed data for the RNG */
	numBytes = RNG_SystemRNG(bytes, sizeof bytes);
	PORT_Assert(numBytes == 0 || numBytes == sizeof bytes);
	if (numBytes != 0) {
	    RNG_RandomUpdate(bytes, numBytes);
	    memset(bytes, 0, numBytes);
	} else if (PORT_GetError() != PR_NOT_IMPLEMENTED_ERROR) {
	    PZ_DestroyLock(globalrng->lock);
	    globalrng->lock = NULL;
	    globalrng->isValid = PR_FALSE;
	    globalrng = NULL;
	    return PR_FAILURE;
	}
	numBytes = RNG_GetNoise(bytes, sizeof bytes);
	RNG_RandomUpdate(bytes, numBytes);
    }
    return PR_SUCCESS;
}

/*
 * Initialize the global RNG context and give it some seed input taken
 * from the system.  This function is thread-safe and will only allow
 * the global context to be initialized once.  The seed input is likely
 * small, so it is imperative that RNG_RandomUpdate() be called with
 * additional seed data before the generator is used.  A good way to
 * provide the generator with additional entropy is to call
 * RNG_SystemInfoForRNG().  Note that NSS_Init() does exactly that.
 */
SECStatus 
RNG_RNGInit(void)
{
    /* Allow only one call to initialize the context */
    PR_CallOnce(&coRNGInit, rng_init);
    /* Make sure there is a context */
    return (globalrng != NULL) ? PR_SUCCESS : PR_FAILURE;
}

/*
** Update the global random number generator with more seeding
** material
*/
static SECStatus 
prng_RandomUpdate(RNGContext *rng, const void *data, size_t bytes)
{
    SECStatus rv = SECSuccess;
    /* check for a valid global RNG context */
    PORT_Assert(rng != NULL);
    if (rng == NULL) {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }
    /* RNG_SystemInfoForRNG() sometimes does this, not really an error */
    if (bytes == 0)
	return SECSuccess;
    /* --- LOCKED --- */
    PZ_Lock(rng->lock);
    /*
     * Random information is initially supplied by a call to
     * RNG_SystemInfoForRNG().  That function collects entropy from
     * the system and calls RNG_RandomUpdate() to seed the generator.
     * Algorithm 1 of FIPS 186-2 Change Notice 1, step 1 specifies that
     * a secret value for the seed-key must be chosen before the
     * generator can begin.  The size of XKEY is b bits, so fill it
     * with the b-bit hash of the input to the first RNG_RandomUpdate()
     * call.
     */
    if (rng->seedCount == 0) {
	/* This is the first call to RandomUpdate().  Use a hash
	 * of the input to set the seed-key, XKEY.
	 *
	 * <Step 1> copy hash of seed bytes into context's XKEY
	 */
	SHA256_HashBuf(rng->XKEY, data, bytes);
	/* Now continue with algorithm. */
	rv = alg_fips186_2_cn_1(rng, NULL);
	/* As per FIPS 140-2 continuous RNG test requirement, the first
	 * iteration of output is discarded.  So here there is really
	 * no output available.  This forces another execution of alg_cn_1()
	 * before any bytes can be extracted from the generator.
	 */
	rng->avail = 0;
    } else if (bytes == BSIZE && memcmp(rng->XKEY, data, BSIZE) == 0) {
	/* Should we add the error code SEC_ERROR_BAD_RNG_SEED? */
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	rv = SECFailure;
    } else {
	/*
	 * FIPS 186-2 does not specify how to reseed the RNG.  We retrofit
	 * our RNG with a reseed function from NIST SP 800-90.
	 *
	 * Use a hash of the seed-key and the input to reseed the RNG.
	 */
	SHA256Context ctx;
	SHA256_Begin(&ctx);
	SHA256_Update(&ctx, rng->XKEY, BSIZE);
	SHA256_Update(&ctx, data, bytes);
	SHA256_End(&ctx, rng->XKEY, NULL, BSIZE);
    }
    /* If got this far, have added bytes of seed data. */
    if (rv == SECSuccess)
	rng->seedCount += bytes;
    PZ_Unlock(rng->lock);
    /* --- UNLOCKED --- */
    return rv;
}

/*
** Update the global random number generator with more seeding
** material.
*/
SECStatus 
RNG_RandomUpdate(const void *data, size_t bytes)
{
    return prng_RandomUpdate(globalrng, data, bytes);
}

/*
** Generate some random bytes, using the global random number generator
** object.
*/
static SECStatus 
prng_GenerateGlobalRandomBytes(RNGContext *rng,
                               void *dest, size_t len)
{
    PRUint8 num;
    SECStatus rv = SECSuccess;
    unsigned char *output = dest;
    /* check for a valid global RNG context */
    PORT_Assert(rng != NULL);
    if (rng == NULL) {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }
    /* --- LOCKED --- */
    PZ_Lock(rng->lock);
    /* Check the amount of seed data in the generator.  If not enough,
     * don't produce any data.
     */
    if (rng->seedCount < MIN_SEED_COUNT) {
	PZ_Unlock(rng->lock);
	PORT_SetError(SEC_ERROR_NEED_RANDOM);
	return SECFailure;
    }
    /*
     * If there are enough bytes of random data, send back Xj, 
     * else call alg_cn_1() with 0's to generate more random data.
     */
    while (len > 0 && rv == SECSuccess) {
	if (rng->avail == 0) {
	    /* All available bytes are used, so generate more. */
	    rv = alg_fips186_2_cn_1(rng, NULL);
	}
	/* number of bytes to obtain on this iteration (max of 40) */
	num = PR_MIN(rng->avail, len);
	/*
	 * if avail < 2*GSIZE, the first 2*GSIZE - avail bytes have
	 * already been used.
	 */
	if (num) {
	    memcpy(output, rng->Xj + (2*GSIZE - rng->avail), num);
	    rng->avail -= num;
	    len -= num;
	    output += num;
	}
    }
    PZ_Unlock(rng->lock);
    /* --- UNLOCKED --- */
    return rv;
}

/*
** Generate some random bytes, using the global random number generator
** object.
*/
SECStatus 
RNG_GenerateGlobalRandomBytes(void *dest, size_t len)
{
    return prng_GenerateGlobalRandomBytes(globalrng, dest, len);
}

void
RNG_RNGShutdown(void)
{
    /* check for a valid global RNG context */
    PORT_Assert(globalrng != NULL);
    if (globalrng == NULL) {
	/* Should set a "not initialized" error code. */
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	return;
    }
    /* clear */
    freeRNGContext();
    /* reset the callonce struct to allow a new call to RNG_RNGInit() */
    coRNGInit = pristineCallOnce;
}

/*
 *  SHA: Generate hash value from context
 *       Specialized function for PRNG
 *       The PRNG specified in FIPS 186-2 3.3 uses a function, G,
 *       which has the same initialization and compression functions
 *       as SHA1 180-1, but uses different padding.  FIPS 186-2 3.3 
 *       specifies that the message be padded with 0's until the size
 *       reaches 512 bits.
 */
static void 
RNG_UpdateAndEnd_FIPS186_2(SHA1Context *ctx, 
                           unsigned char *input, unsigned int inputLen,
                           unsigned char *hashout, unsigned int *pDigestLen, 
                           unsigned int maxDigestLen)
{
#if defined(SHA_NEED_TMP_VARIABLE)
    register PRUint32 tmp;
#endif
    static const unsigned char bulk_pad0[64] = { 0,0,0,0,0,0,0,0,0,0,
               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
               0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0  };

    PORT_Assert(maxDigestLen >= SHA1_LENGTH);
    PORT_Assert(inputLen <= SHA1_INPUT_LEN);

    /*
     *  Add the input
     */
    SHA1_Update(ctx, input, inputLen);
    /*
     *  Pad with zeroes
     *  This will fill the input block and cause the compression function
     *  to be called.
     */
    SHA1_Update(ctx, bulk_pad0, SHA1_INPUT_LEN - inputLen);

    /*
     *  Output hash
     */
    SHA_STORE_RESULT;
    *pDigestLen = SHA1_LENGTH;
}

/*
 * Specialized RNG for DSA
 *
 * As per Algorithm 1 of FIPS 186-2 Change Notice 1, in step 3.3 the value
 * Xj should be reduced mod q, a 160-bit prime number.  Since this parameter
 * is only meaningful in the context of DSA, the above RNG functions
 * were implemented without it.  They are re-implemented below for use
 * with DSA.
 *
 */

/*
** Generate some random bytes, using the global random number generator
** object.  In DSA mode, so there is a q.
*/
SECStatus 
DSA_GenerateGlobalRandomBytes(void *dest, size_t len, const unsigned char *q)
{
    SECStatus rv;
    unsigned char w[2*GSIZE];

    PORT_Assert(q && len == DSA_SUBPRIME_LEN);
    if (len != DSA_SUBPRIME_LEN) {
	PORT_SetError(SEC_ERROR_OUTPUT_LEN);
	return SECFailure;
    }
    if (*q == 0) {
        ++q;
    }
    rv = prng_GenerateGlobalRandomBytes(globalrng, w, 2*GSIZE);
    if (rv != SECSuccess) {
	return rv;
    }
    FIPS186Change_ReduceModQForDSA(w, q, (unsigned char *)dest);
    return rv;
}
