/** @file
  OpenSSL_1_1_1b doesn't implement rand_pool_* functions for UEFI.
  The file implement these functions.

Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "internal/rand_int.h"
#include <openssl/aes.h>

#include <Uefi.h>
#include <Library/TimerLib.h>

#include "rand_pool_noise.h"

/**
  Get some randomness from low-order bits of GetPerformanceCounter results.
  And combine them to the 64-bit value

  @param[out] Rand    Buffer pointer to store the 64-bit random value.

  @retval TRUE        Random number generated successfully.
  @retval FALSE       Failed to generate.
**/
STATIC
BOOLEAN
EFIAPI
GetRandNoise64FromPerformanceCounter(
  OUT UINT64      *Rand
  )
{
  UINT32 Index;
  UINT32 *RandPtr;

  if (NULL == Rand) {
    return FALSE;
  }

  RandPtr = (UINT32 *) Rand;

  for (Index = 0; Index < 2; Index ++) {
    *RandPtr = (UINT32) (GetPerformanceCounter () & 0xFF);
    MicroSecondDelay (10);
    RandPtr++;
  }

  return TRUE;
}

/**
  Calls RandomNumber64 to fill
  a buffer of arbitrary size with random bytes.

  @param[in]   Length        Size of the buffer, in bytes,  to fill with.
  @param[out]  RandBuffer    Pointer to the buffer to store the random result.

  @retval EFI_SUCCESS        Random bytes generation succeeded.
  @retval EFI_NOT_READY      Failed to request random bytes.

**/
STATIC
BOOLEAN
EFIAPI
RandGetBytes (
  IN UINTN         Length,
  OUT UINT8        *RandBuffer
  )
{
  BOOLEAN     Ret;
  UINT64      TempRand;

  Ret = FALSE;

  while (Length > 0) {
    //
    // Get random noise from platform.
    // If it failed, fallback to PerformanceCounter
    // If you really care about security, you must override
    // GetRandomNoise64FromPlatform.
    //
    Ret = GetRandomNoise64 (&TempRand);
    if (Ret == FALSE) {
      Ret = GetRandNoise64FromPerformanceCounter (&TempRand);
    }
    if (!Ret) {
      return Ret;
    }
    if (Length >= sizeof (TempRand)) {
      *((UINT64*) RandBuffer) = TempRand;
      RandBuffer += sizeof (UINT64);
      Length -= sizeof (TempRand);
    } else {
      CopyMem (RandBuffer, &TempRand, Length);
      Length = 0;
    }
  }

  return Ret;
}

/**
  Creates a 128bit random value that is fully forward and backward prediction resistant,
  suitable for seeding a NIST SP800-90 Compliant.
  This function takes multiple random numbers from PerformanceCounter to ensure reseeding
  and performs AES-CBC-MAC over the data to compute the seed value.

  @param[out]  SeedBuffer    Pointer to a 128bit buffer to store the random seed.

  @retval TRUE        Random seed generation succeeded.
  @retval FALSE      Failed to request random bytes.

**/
STATIC
BOOLEAN
EFIAPI
RandGetSeed128 (
  OUT UINT8        *SeedBuffer
  )
{
  BOOLEAN     Ret;
  UINT8       RandByte[16];
  UINT8       Key[16];
  UINT8       Ffv[16];
  UINT8       Xored[16];
  UINT32      Index;
  UINT32      Index2;
  AES_KEY     AESKey;

  //
  // Chose an arbitary key and zero the feed_forward_value (FFV)
  //
  for (Index = 0; Index < 16; Index++) {
    Key[Index] = (UINT8) Index;
    Ffv[Index] = 0;
  }

  AES_set_encrypt_key (Key, 16 * 8, &AESKey);

  //
  // Perform CBC_MAC over 32 * 128 bit values, with 10us gaps between 128 bit value
  // The 10us gaps will ensure multiple reseeds within the system time with a large
  // design margin.
  //
  for (Index = 0; Index < 32; Index++) {
    MicroSecondDelay (10);
    Ret = RandGetBytes (16, RandByte);
    if (!Ret) {
      return Ret;
    }

    //
    // Perform XOR operations on two 128-bit value.
    //
    for (Index2 = 0; Index2 < 16; Index2++) {
      Xored[Index2] = RandByte[Index2] ^ Ffv[Index2];
    }

    AES_encrypt (Xored, Ffv, &AESKey);
  }

  for (Index = 0; Index < 16; Index++) {
    SeedBuffer[Index] = Ffv[Index];
  }

  return Ret;
}

/**
  Generate high-quality entropy source.

  @param[in]   Length        Size of the buffer, in bytes, to fill with.
  @param[out]  Entropy       Pointer to the buffer to store the entropy data.

  @retval EFI_SUCCESS        Entropy generation succeeded.
  @retval EFI_NOT_READY      Failed to request random data.

**/
STATIC
BOOLEAN
EFIAPI
RandGenerateEntropy (
  IN UINTN         Length,
  OUT UINT8        *Entropy
  )
{
  BOOLEAN     Ret;
  UINTN       BlockCount;
  UINT8       Seed[16];
  UINT8       *Ptr;

  BlockCount = Length / 16;
  Ptr        = (UINT8 *) Entropy;

  //
  // Generate high-quality seed for DRBG Entropy
  //
  while (BlockCount > 0) {
    Ret = RandGetSeed128 (Seed);
    if (!Ret) {
      return Ret;
    }
    CopyMem (Ptr, Seed, 16);

    BlockCount--;
    Ptr = Ptr + 16;
  }

  //
  // Populate the remained data as request.
  //
  Ret = RandGetSeed128 (Seed);
  if (!Ret) {
    return Ret;
  }
  CopyMem (Ptr, Seed, (Length % 16));

  return Ret;
}

/*
 * Add random bytes to the pool to acquire requested amount of entropy
 *
 * This function is platform specific and tries to acquire the requested
 * amount of entropy by polling platform specific entropy sources.
 *
 * This is OpenSSL required interface.
 */
size_t rand_pool_acquire_entropy(RAND_POOL *pool)
{
  BOOLEAN  Ret;
  size_t bytes_needed;
  unsigned char * buffer;

  bytes_needed = rand_pool_bytes_needed(pool, 1 /*entropy_factor*/);
  if (bytes_needed > 0) {
    buffer = rand_pool_add_begin(pool, bytes_needed);

    if (buffer != NULL) {
      Ret = RandGenerateEntropy(bytes_needed, buffer);
      if (FALSE == Ret) {
        rand_pool_add_end(pool, 0, 0);
      } else {
        rand_pool_add_end(pool, bytes_needed, 8 * bytes_needed);
      }
    }
  }

  return rand_pool_entropy_available(pool);
}

/*
 * Implementation for UEFI
 *
 * This is OpenSSL required interface.
 */
int rand_pool_add_nonce_data(RAND_POOL *pool)
{
  struct {
    UINT64  Rand;
    UINT64  TimerValue;
  } data = { 0 };

  RandGetBytes(8, (UINT8 *)&(data.Rand));
  data.TimerValue = GetPerformanceCounter();

  return rand_pool_add(pool, (unsigned char*)&data, sizeof(data), 0);
}

/*
 * Implementation for UEFI
 *
 * This is OpenSSL required interface.
 */
int rand_pool_add_additional_data(RAND_POOL *pool)
{
  struct {
    UINT64  Rand;
    UINT64  TimerValue;
  } data = { 0 };

  RandGetBytes(8, (UINT8 *)&(data.Rand));
  data.TimerValue = GetPerformanceCounter();

  return rand_pool_add(pool, (unsigned char*)&data, sizeof(data), 0);
}

/*
 * Dummy Implememtation for UEFI
 *
 * This is OpenSSL required interface.
 */
int rand_pool_init(void)
{
  return 1;
}

/*
 * Dummy Implememtation for UEFI
 *
 * This is OpenSSL required interface.
 */
void rand_pool_cleanup(void)
{
}

/*
 * Dummy Implememtation for UEFI
 *
 * This is OpenSSL required interface.
 */
void rand_pool_keep_random_devices_open(int keep)
{
}

