/*++ Copyright (c) 2015 Minoca Corp. All Rights Reserved Module Name: fortuna.c Abstract: This module implements support for the Fortuna Pseudo-Random Number Generator. Author: Evan Green 13-Jan-2015 Environment: Any --*/ // // ------------------------------------------------------------------- Includes // #include "cryptop.h" // // ---------------------------------------------------------------- Definitions // #define FORTUNA_POOL0_FILL 32 #define FORTUNA_RESEED_SIZE (1024 * 1024) #define FORTUNA_RESEED_INTERVAL_MILLISECONDS 100 // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // VOID CypFortunaSpreadInitialSeed ( PFORTUNA_CONTEXT Context ); VOID CypFortunaRekey ( PFORTUNA_CONTEXT Context ); BOOL CypFortunaCheckReseedTime ( PFORTUNA_CONTEXT Context ); VOID CypFortunaReseed ( PFORTUNA_CONTEXT Context ); VOID CypFortunaEncryptCounter ( PFORTUNA_CONTEXT Context, UCHAR Buffer[FORTUNA_BLOCK_SIZE] ); VOID CypFortunaIncrementCounter ( PFORTUNA_CONTEXT Context ); UINTN CypFortunaGetRandomPoolIndex ( PFORTUNA_CONTEXT Context ); // // -------------------------------------------------------------------- Globals // // // ------------------------------------------------------------------ Functions // CRYPTO_API VOID CyFortunaInitialize ( PFORTUNA_CONTEXT Context, PCY_GET_TIME_COUNTER GetTimeCounterFunction, ULONGLONG TimeCounterFrequency ) /*++ Routine Description: This routine initializes a Fortuna PRNG context. It does not seed it with any values. Arguments: Context - Supplies a pointer to the context. GetTimeCounterFunction - Supplies an optional pointer to a function that can be used to retrieve a monotonically non-decreasing value representing the passage of time since some epoch. TimeCounterFrequency - Supplies the frequency of the time counter in Hertz. Return Value: None. --*/ { UINTN Pool; RtlZeroMemory(Context, sizeof(FORTUNA_CONTEXT)); for (Pool = 0; Pool < FORTUNA_POOL_COUNT; Pool += 1) { CySha256Initialize(&(Context->Pools[Pool])); } Context->GetTimeCounter = GetTimeCounterFunction; Context->TimeCounterFrequency = TimeCounterFrequency; return; } CRYPTO_API VOID CyFortunaGetRandomBytes ( PFORTUNA_CONTEXT Context, PUCHAR Data, UINTN Size ) /*++ Routine Description: This routine returns random bytes from a Fortuna instance. Arguments: Context - Supplies a pointer to the context. Data - Supplies a pointer where the random bytes will be returned. Size - Supplies the number of bytes to return. Return Value: None. --*/ { UINTN BlockNumber; UINTN CopySize; BlockNumber = 0; ASSERT(Context->Initialized != FortunaNotInitialized); // // Spread the seed around a bit if this is the first time. // if (Context->Initialized < FortunaInitialized) { CypFortunaSpreadInitialSeed(Context); Context->Initialized = FortunaInitialized; } // // Determine if a reseed should occur. // if ((Context->Pool0Bytes >= FORTUNA_POOL0_FILL) || (Context->ReseedCount == 0)) { if (CypFortunaCheckReseedTime(Context) != FALSE) { CypFortunaReseed(Context); } } while (Size > 0) { CypFortunaEncryptCounter(Context, Context->Result); CopySize = Size; if (CopySize > FORTUNA_BLOCK_SIZE) { CopySize = FORTUNA_BLOCK_SIZE; } RtlCopyMemory(Data, Context->Result, CopySize); Data += CopySize; Size -= CopySize; // // Avoid giving out too many bytes from a single key. // BlockNumber += 1; if (BlockNumber > (FORTUNA_RESEED_SIZE / FORTUNA_BLOCK_SIZE)) { CypFortunaRekey(Context); BlockNumber = 0; } } // // Re-key for the next request. // CypFortunaRekey(Context); return; } CRYPTO_API VOID CyFortunaAddEntropy ( PFORTUNA_CONTEXT Context, PVOID Data, UINTN Size ) /*++ Routine Description: This routine adds random data into the mix. Arguments: Context - Supplies a pointer to the context. Data - Supplies a pointer to the data to add. Size - Supplies the number of bytes of randomness in the data buffer. Return Value: None. --*/ { UCHAR Hash[SHA256_HASH_SIZE]; SHA256_CONTEXT HashContext; UINTN PoolIndex; // // Hash the data handed in. // CySha256Initialize(&HashContext); CySha256AddContent(&HashContext, Data, Size); CySha256GetHash(&HashContext, Hash); // // Make sure pool zero is initialized, otherwise update randomly. // if (Context->ReseedCount == 0) { PoolIndex = 0; if (Context->Initialized == FortunaNotInitialized) { Context->Initialized = FortunaInitializationSeeded; } } else { PoolIndex = CypFortunaGetRandomPoolIndex(Context); } CySha256AddContent(&(Context->Pools[PoolIndex]), Hash, SHA256_HASH_SIZE); if (PoolIndex == 0) { Context->Pool0Bytes += SHA256_HASH_SIZE; } return; } // // --------------------------------------------------------- Internal Functions // VOID CypFortunaSpreadInitialSeed ( PFORTUNA_CONTEXT Context ) /*++ Routine Description: This routine spreads the entropy gained so far around the pools. Arguments: Context - Supplies a pointer to the context. Return Value: None. --*/ { UCHAR Buffer[FORTUNA_HASH_KEY_SIZE]; UINTN PoolIndex; // // Use the next block as the initial counter value. // CypFortunaEncryptCounter(Context, Context->Counter); // // Shuffle all pools except pool zero. // for (PoolIndex = 0; PoolIndex < FORTUNA_POOL_COUNT; PoolIndex += 1) { CypFortunaEncryptCounter(Context, Buffer); CypFortunaEncryptCounter(Context, Buffer + FORTUNA_BLOCK_SIZE); CySha256AddContent(&(Context->Pools[PoolIndex]), Buffer, FORTUNA_HASH_KEY_SIZE); } RtlZeroMemory(Buffer, sizeof(Buffer)); // // Hide the key. // CypFortunaRekey(Context); return; } VOID CypFortunaRekey ( PFORTUNA_CONTEXT Context ) /*++ Routine Description: This routine selects a different cipher key for use in future block generation. Arguments: Context - Supplies a pointer to the context. Return Value: None. --*/ { // // Use the two next blocks as the new key. // CypFortunaEncryptCounter(Context, Context->Key); CypFortunaEncryptCounter(Context, Context->Key + FORTUNA_BLOCK_SIZE); CyAesInitialize(&(Context->CipherContext), AesModeCbc256, Context->Key, NULL); return; } BOOL CypFortunaCheckReseedTime ( PFORTUNA_CONTEXT Context ) /*++ Routine Description: This routine determines whether it is time for the context to be reseeded or not. Arguments: Context - Supplies a pointer to the context. Return Value: TRUE if enough time has passed such that the context can be reseeded. FALSE if it hasn't been long enough since the last reseed. --*/ { ULONGLONG CurrentTime; ULONGLONG DeltaTicks; ULONGLONG Milliseconds; if ((Context->GetTimeCounter == NULL) || (Context->TimeCounterFrequency == 0)) { return TRUE; } CurrentTime = Context->GetTimeCounter(); DeltaTicks = CurrentTime - Context->LastReseedTime; // // Compute the number of milliseconds since the last update. // Milliseconds = (DeltaTicks * 1000ULL) / Context->TimeCounterFrequency; if (Milliseconds >= FORTUNA_RESEED_INTERVAL_MILLISECONDS) { Context->LastReseedTime = CurrentTime; return TRUE; } return FALSE; } VOID CypFortunaReseed ( PFORTUNA_CONTEXT Context ) /*++ Routine Description: This routine selects a completely new cipher key using the entropy pools. Arguments: Context - Supplies a pointer to the context. Return Value: None. --*/ { UCHAR Buffer[FORTUNA_HASH_KEY_SIZE]; SHA256_CONTEXT KeyHashContext; UINTN PoolIndex; UINTN ReseedCount; // // Mark the pool as empty. // Context->Pool0Bytes = 0; Context->ReseedCount += 1; ReseedCount = Context->ReseedCount; // // The 0th pool is used every 2nd time, the 1st pool is used every 4th // time, the 2nd pool is used every 8th time, etc. Pool i is used every // 2^ith time. // CySha256Initialize(&KeyHashContext); for (PoolIndex = 0; PoolIndex < FORTUNA_POOL_COUNT; PoolIndex += 1) { CySha256GetHash(&(Context->Pools[PoolIndex]), Buffer); CySha256AddContent(&KeyHashContext, Buffer, FORTUNA_HASH_KEY_SIZE); if (((ReseedCount & 0x1) != 0) || (ReseedCount == 0)) { break; } ReseedCount >>= 1; } // // Add the old key into the mix too. // CySha256AddContent(&KeyHashContext, Context->Key, FORTUNA_HASH_KEY_SIZE); // // Get the new key. // CySha256GetHash(&KeyHashContext, Context->Key); // // Use the new key in future cipher blocks. // CyAesInitialize(&(Context->CipherContext), AesModeCbc256, Context->Key, NULL); // // Avoid leaking state onto the stack. // RtlZeroMemory(Buffer, sizeof(Buffer)); RtlZeroMemory(&KeyHashContext, sizeof(SHA256_CONTEXT)); return; } VOID CypFortunaEncryptCounter ( PFORTUNA_CONTEXT Context, UCHAR Buffer[FORTUNA_BLOCK_SIZE] ) /*++ Routine Description: This routine simply encrypts a new counter value. Arguments: Context - Supplies a pointer to the context. Buffer - Supplies a pointer where the new block will be returned. Return Value: None. --*/ { CyAesCbcEncrypt(&(Context->CipherContext), Context->Counter, Buffer, FORTUNA_BLOCK_SIZE); CypFortunaIncrementCounter(Context); return; } VOID CypFortunaIncrementCounter ( PFORTUNA_CONTEXT Context ) /*++ Routine Description: This routine simply increments the current counter value. Arguments: Context - Supplies a pointer to the context. Return Value: None. --*/ { PUINTN Value; Value = (PUINTN)(Context->Counter); Value[0] += 1; if (Value[0] != 0) { return; } Value[1] += 1; if (Value[1] != 0) { return; } Value[2] += 1; if (Value[2] != 0) { return; } Value[3] += 1; return; } UINTN CypFortunaGetRandomPoolIndex ( PFORTUNA_CONTEXT Context ) /*++ Routine Description: This routine returns a pool index to feed entropy into. Arguments: Context - Supplies a pointer to the context. Return Value: Returns a pool index. --*/ { UINTN Index; Index = Context->Key[Context->Position] % FORTUNA_POOL_COUNT; Context->Position += 1; if (Context->Position >= FORTUNA_HASH_KEY_SIZE) { Context->Position = 0; } return Index; }