diff options
Diffstat (limited to 'security/nss/lib/freebl/prng_fips1861.c')
-rw-r--r-- | security/nss/lib/freebl/prng_fips1861.c | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/security/nss/lib/freebl/prng_fips1861.c b/security/nss/lib/freebl/prng_fips1861.c new file mode 100644 index 000000000..bf0199926 --- /dev/null +++ b/security/nss/lib/freebl/prng_fips1861.c @@ -0,0 +1,513 @@ +/* + * + * 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 Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + * + * $Id$ + */ + +#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 "secrng.h" /* for RNG_GetNoise() */ + +/* + * 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 FIPS 186-1 Appendix 3.1 + */ + +/* + * According to FIPS 186-1, 160 <= b <= 512 + * For our purposes, we will assume b == 160 + */ +#define FIPS_B 160 +#define BSIZE FIPS_B / BITS_PER_BYTE + +/* + * Add two 160-bit numbers represented as arrays of 20 bytes. + * The numbers are big-endian, MSB first, so addition is done + * from the end of the buffer to the beginning. + */ +#define ADD_160BIT_PLUS_CARRY(dest, add1, add2, cy) \ + carry = cy; \ + for (i=BSIZE-1; i>=0; --i) { \ + carry += add1[i] + add2[i]; \ + dest[i] = (PRUint8)carry; \ + carry >>= 8; \ + } + +#define ADD_160BIT_2(dest, add1, add2) \ + ADD_160BIT_PLUS_CARRY(dest, add1, add2, 0) + + +/* + * FIPS requires result from Step 3c to be reduced mod q when generating + * random numbers for DSA. + * by definition q >= 2^159 + 1, thus xj < 2q + * thus reducing mod q is simple subtraction when xj > q + */ +#define dsa_reduce_mod_q(xj, q) \ + PORT_Assert(q[0] >= 0x80); \ + if (memcmp(xj,q,BSIZE) > 0) { \ + carry = 0; \ + for (i=BSIZE-1; i>=0; --i) { \ + carry += (signed int)xj[i] - (signed int)q[i]; \ + xj[i] = (PRUint8)carry; \ + carry >>= 8; \ + } \ + } + +/* + * 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-1 appendix 3.3. + */ +void +RNG_UpdateAndEnd_FIPS186_1(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[BSIZE]; /* Output from previous operation */ + PZLock *lock; /* Lock to serialize access to global rng */ + PRUint8 avail; /* # bytes of output available, [0...20] */ + 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; + +/* + * Free the global RNG context + */ +static void +freeRNGContext() +{ + PZ_DestroyLock(globalrng->lock); + PORT_ZFree(globalrng, sizeof *globalrng); + globalrng = NULL; +} + +/* + * Implementation of the algorithm in FIPS 186-1 appendix 3.1, heretofore + * called alg3_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_1_x3_1(RNGContext *rng, + const unsigned char *XSEEDj, unsigned char *q) +{ + /* SHA1 context for G(t, XVAL) function */ + SHA1Context sha1cx; + /* input to hash function */ + PRUint8 XVAL[BSIZE]; + /* store a copy of the output to compare with the previous output */ + PRUint8 x_j[BSIZE]; + /* used by ADD_160BIT macros */ + int i, carry; + unsigned int len; + if (!rng->isValid) { + /* RNG has alread entered an invalid state. */ + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + /* initialize the SHA1 context */ + memset(&sha1cx, 0, sizeof(sha1cx)); + /* + * <Step 2> Initialize t, taken care of in SHA-1 (same initial values) + */ + SHA1_Begin(&sha1cx); + /* + * <Step 3a> XSEEDj is optional user input + * + * <Step 3b> XVAL = (XKEY + XSEEDj) mod 2^b + * :always reduced mod 2^b, since storing as 160-bit value + */ + if (XSEEDj) { + /* XSEEDj > 0 */ + ADD_160BIT_2(XVAL, rng->XKEY, XSEEDj); + } else { + /* XSEEDj == 0 */ + memcpy(XVAL, rng->XKEY, BSIZE); + } + /* + * <Step 3c> Xj = G(t, XVAL) mod q + * :In this code, (mod q) is only understood for DSA ops, + * :not general RNG (what would q be in non-DSA uses?). + * :If a q is specified, use it. + * :FIPS 186-1 specifies a different padding than the SHA1 180-1 + * :specification, this function is implemented below. + */ + RNG_UpdateAndEnd_FIPS186_1(&sha1cx, XVAL, BSIZE, x_j, &len, BSIZE); + if (q != NULL) { + dsa_reduce_mod_q(x_j, q); + } + /* [FIPS 140-1] verify output does not match previous output */ + if (memcmp(x_j, rng->Xj, BSIZE) == 0) { + /* failed FIPS 140-1 continuous RNG condition. RNG now invalid. */ + rng->isValid = PR_FALSE; + return SECFailure; + } + /* Xj is the output */ + memcpy(rng->Xj, x_j, BSIZE); + /* + * <Step 3d> XKEY = (1 + XKEY + Xj) mod 2^b + * :always reduced mod 2^b, since storing as 160-bit value + */ + ADD_160BIT_PLUS_CARRY(rng->XKEY, rng->XKEY, x_j, 1); + /* Always have a full buffer after executing alg3_1() */ + rng->avail = BSIZE; + /* housekeeping */ + memset(x_j, 0, BSIZE); + memset(XVAL, 0, BSIZE); + return SECSuccess; +} + +/* Use NSPR to prevent RNG_RNGInit from being called from separate + * threads, creating a race condition. + */ +static PRCallOnceType coRNGInit = { 0, 0, 0 }; +static PRStatus rng_init(void) +{ + unsigned char bytes[120]; + unsigned int numBytes; + if (globalrng == NULL) { + /* create a new global RNG context */ + globalrng = (RNGContext *)PORT_ZAlloc(sizeof(RNGContext)); + if (globalrng == NULL) { + PORT_SetError(PR_OUT_OF_MEMORY_ERROR); + return PR_FAILURE; + } + /* create a lock for it */ + globalrng->lock = PZ_NewLock(nssILockOther); + if (globalrng->lock == 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_GetNoise(bytes, sizeof bytes); + RNG_RandomUpdate(bytes, numBytes); + } + return (globalrng != NULL) ? PR_SUCCESS : PR_FAILURE; +} + +/* + * 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 +*/ +SECStatus +prng_RandomUpdate(RNGContext *rng, const void *data, size_t bytes, + unsigned char *q) +{ + SECStatus rv = SECSuccess; + unsigned char inputhash[BSIZE]; + /* 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; + /* If received 20 bytes of input, use it, else hash the input before + * locking. + */ + if (bytes == BSIZE) + memcpy(inputhash, data, BSIZE); + else + rv = SHA1_HashBuf(inputhash, data, bytes); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + /* --- 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. + * FIPS 186-1 3.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-bytes, so fill it with the first b-bytes + * sent to RNG_RandomUpdate(). + */ + if (rng->seedCount == 0) { + /* This is the first call to RandomUpdate(). Use a SHA1 hash + * of the input to set the seed, XKEY. + * + * <Step 1> copy seed bytes into context's XKEY + */ + memcpy(rng->XKEY, inputhash, BSIZE); + /* + * Now continue with algorithm. Since the input was used to + * initialize XKEY, the "optional user input" at this stage + * will be a pad of zeros, XSEEDj = 0. + */ + rv = alg_fips186_1_x3_1(rng, NULL, q); + /* As per FIPS 140-1 continuous RNG requirement, the first + * iteration of output is discarded. So here there is really + * no output available. This forces another execution of alg3_1() + * before any bytes can be extracted from the generator. + */ + rng->avail = 0; + } else { + /* Execute the algorithm from FIPS 186-1 appendix 3.1 */ + rv = alg_fips186_1_x3_1(rng, inputhash, q); + } + /* If got this far, have added bytes of seed data. */ + rng->seedCount += bytes; + PZ_Unlock(rng->lock); + /* --- UNLOCKED --- */ + /* housekeeping */ + memset(inputhash, 0, BSIZE); + return rv; +} + +/* +** Update the global random number generator with more seeding +** material. Not DSA, so no q. +*/ +SECStatus +RNG_RandomUpdate(const void *data, size_t bytes) +{ + return prng_RandomUpdate(globalrng, data, bytes, NULL); +} + +/* +** Generate some random bytes, using the global random number generator +** object. +*/ +SECStatus +prng_GenerateGlobalRandomBytes(RNGContext *rng, + void *dest, size_t len, unsigned char *q) +{ + 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 alg3_1() with 0's to generate more random data. + */ + while (len > 0) { + if (rng->avail == 0) + /* All available bytes are used, so generate more. */ + rv = alg_fips186_1_x3_1(rng, NULL, q); + /* number of bytes to obtain on this iteration (max of 20) */ + num = PR_MIN(rng->avail, len); + /* if avail < BSIZE, the first avail bytes have already been used. */ + memcpy(output, rng->Xj + (BSIZE - 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. Not DSA, so no q. +*/ +SECStatus +RNG_GenerateGlobalRandomBytes(void *dest, size_t len) +{ + return prng_GenerateGlobalRandomBytes(globalrng, dest, len, NULL); +} + +void +RNG_RNGShutdown(void) +{ + /* check for a valid global RNG context */ + PORT_Assert(globalrng != NULL); + if (globalrng == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return; + } + /* clear */ + freeRNGContext(); + /* zero the callonce struct to allow a new call to RNG_RNGInit() */ + memset(&coRNGInit, 0, sizeof coRNGInit); +} + +/* + * SHA: Generate hash value from context + * Specialized function for PRNG + * The PRNG specified in FIPS 186-1 3.3 uses a function, G, + * which has the same initialization and compression functions + * as SHA1 180-1, but uses different padding. FIPS 186-1 3.3 + * specifies that the message be padded with 0's until the size + * reaches 512 bits. + */ +void +RNG_UpdateAndEnd_FIPS186_1(SHA1Context *ctx, + unsigned char *input, unsigned int inputLen, + unsigned char *hashout, unsigned int *pDigestLen, + unsigned int maxDigestLen) +{ + register PRUint32 A; + 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 + */ +#if defined(IS_LITTLE_ENDIAN) + SHA_BYTESWAP(ctx->H[0]); + SHA_BYTESWAP(ctx->H[1]); + SHA_BYTESWAP(ctx->H[2]); + SHA_BYTESWAP(ctx->H[3]); + SHA_BYTESWAP(ctx->H[4]); +#endif + memcpy(hashout, ctx->H, SHA1_LENGTH); + *pDigestLen = SHA1_LENGTH; + + /* + * Re-initialize the context (also zeroizes contents) + */ + SHA1_Begin(ctx); +} + +/* + * Specialized RNG for DSA + * + * As per FIPS 186-1 appendix 3.1, in step 5 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. + * + */ + +/* +** Update the global random number generator with more seeding +** material. DSA needs a q parameter. +*/ +SECStatus +DSA_RandomUpdate(void *data, size_t bytes, unsigned char *q) +{ + if( q && (*q == 0) ) { + ++q; + } + return prng_RandomUpdate(globalrng, data, bytes, q); +} + +/* +** 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, unsigned char *q) +{ + if( q && (*q == 0) ) { + ++q; + } + return prng_GenerateGlobalRandomBytes(globalrng, dest, len, q); +} |