// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/rand.h>
#include <openssl/mem.h>
#include <openssl/ctrdrbg.h>
#include <openssl/type_check.h>

#include "entropy/internal.h"
#include "internal.h"
#include "../../internal.h"
#include "../../ube/internal.h"
#include "../delocate.h"
#include "../service_indicator/internal.h"

// rand_thread_state contains the per-thread state for the RNG.
struct rand_thread_local_state {
  // Thread-local CTR-DRBG state. UBE unique state.
  CTR_DRBG_STATE drbg;

  // generate_calls_since_seed is the number of generate calls made on |drbg|
  // since it was last (re)seeded. Must be bounded by |kCtrDrbgReseedInterval|.
  uint64_t generate_calls_since_seed;

  // reseed_calls_since_initialization is the number of reseed calls made on
  // |drbg| since its initialization.
  // We assume 2^64 - 1 is an upper bound on the number of reseeds. Type must
  // support that.
  uint64_t reseed_calls_since_initialization;

  // generation_number caches the UBE generation number.
  uint64_t generation_number;

  // Entropy source. UBE unique state.
  struct entropy_source_t *entropy_source;

  // Backward and forward references to nodes in a doubly-linked list.
  struct rand_thread_local_state *next;
  struct rand_thread_local_state *previous;

  // Lock used when globally clearing (zeroising) all thread-local states at
  // process exit.
  CRYPTO_MUTEX state_clear_lock;
};
OPENSSL_STATIC_ASSERT((sizeof((struct rand_thread_local_state*)0)->generate_calls_since_seed) * 8 >= 48, value_can_overflow)

DEFINE_BSS_GET(struct rand_thread_local_state *, thread_states_list_head)
DEFINE_STATIC_MUTEX(thread_local_states_list_lock)

#if defined(_MSC_VER)
#pragma section(".CRT$XCU", read)
static void rand_thread_local_state_clear_all(void);
static void windows_install_rand_thread_local_state_clear_all(void) {
  atexit(&rand_thread_local_state_clear_all);
}
__declspec(allocate(".CRT$XCU")) void(*rand_fips_library_destructor)(void) =
    windows_install_rand_thread_local_state_clear_all;
#else
static void rand_thread_local_state_clear_all(void) __attribute__ ((destructor));
#endif

// At process exit not all threads will be scheduled and proper exited. To
// ensure no secret state is left, globally clear all thread-local states. This
// is a FIPS-derived requirement, see ISO/IEC 19790-2012 7.9.7.
//
// This is problematic because a thread might be scheduled and return
// randomness from a non-valid state. The linked application should obviously
// arrange that all threads are gracefully exited before exiting the process.
// Yet, in cases where such graceful exit does not happen we ensure that no
// output can be returned by locking all thread-local states and deliberately
// not releasing the lock. A synchronization step in the core randomness
// generation routine |RAND_bytes_core| then ensures that no randomness
// generation can occur after a thread-local state has been locked. It also
// ensures |rand_thread_local_state_free| cannot free any thread state while we
// own the lock.
//
// When a thread-local DRBGs is gated from returning output, we can invoke the
// entropy source zeroization from |state->entropy_source|. The entropy source
// implementation can assume that any returned seed is never used to generate
// any randomness that is later returned to a consumer.
static void rand_thread_local_state_clear_all(void) {
  CRYPTO_STATIC_MUTEX_lock_write(thread_local_states_list_lock_bss_get());
  for (struct rand_thread_local_state *state = *thread_states_list_head_bss_get();
    state != NULL; state = state->next) {
    CRYPTO_MUTEX_lock_write(&state->state_clear_lock);
    CTR_DRBG_clear(&state->drbg);
  }

  for (struct rand_thread_local_state *state = *thread_states_list_head_bss_get();
    state != NULL; state = state->next) {
    state->entropy_source->methods->zeroize_thread(state->entropy_source);
  }
}

static void thread_local_list_delete_node(
  struct rand_thread_local_state *node_delete) {

  // Mutating the global linked list. Need to synchronize over all threads.
  CRYPTO_STATIC_MUTEX_lock_write(thread_local_states_list_lock_bss_get());
  struct rand_thread_local_state *node_head = *thread_states_list_head_bss_get();

  // We have [node_delete->previous] <--> [node_delete] <--> [node_delete->next]
  // and must end up with [node_delete->previous] <--> [node_delete->next]
  if (node_head == node_delete) {
    // If node node_delete is the head, we know that the backwards reference
    // does not exist but we need to update the head pointer.
    *thread_states_list_head_bss_get() = node_delete->next;
  } else {
    // On the other hand, if node_delete is not the head, then we need to update
    // the node node_delete->previous to point to the node node_delete->next.
    // But only if node_delete->previous actually exists.
    if (node_delete->previous != NULL) {
      (node_delete->previous)->next = node_delete->next;
    }
  }

  // Now [node_delete->previous] --> [node_delete->next]
  //                                          |
  //                         [node_delete] <--|
  // Final thing to do is to update the backwards reference for the node
  // node_delete->next, if it exists.
  if (node_delete->next != NULL) {
    // If node_delete is the head, then node_delete->previous is NULL. But that
    // is OK because node_delete->next is the new head and should therefore have
    // a backwards reference that is NULL.
    (node_delete->next)->previous = node_delete->previous;
  }

  CRYPTO_STATIC_MUTEX_unlock_write(thread_local_states_list_lock_bss_get());
}

// thread_local_list_add adds the state |node_add| to the linked list. Note that
// |node_add| is not added at the tail of the linked list, but is replacing the
// current head to keep the add operation at low time-complexity.
static void thread_local_list_add_node(
  struct rand_thread_local_state *node_add) {

  // node_add will be the new head and will not have a backwards reference.
  node_add->previous = NULL;

  // Mutating the global linked list. Need to synchronize over all threads.
  CRYPTO_STATIC_MUTEX_lock_write(thread_local_states_list_lock_bss_get());

  // First get a reference to the pointer of the head of the linked list.
  // That is, the pointer to the head node node_head is *thread_states_head.
  struct rand_thread_local_state **thread_states_head = thread_states_list_head_bss_get();

  // We have [node_head] <--> [node_head->next] and must end up with
  // [node_add] <--> [node_head] <--> [node_head->next]
  // First make the forward reference
  node_add->next = *thread_states_head;

  // Only add a backwards reference if a head already existed (this might be
  // the first add).
  if (*thread_states_head != NULL) {
    (*thread_states_head)->previous = node_add;
  }

  // The last thing is to assign the new head.
  *thread_states_head = node_add;

  CRYPTO_STATIC_MUTEX_unlock_write(thread_local_states_list_lock_bss_get());
}

// rand_thread_local_state frees a |rand_thread_local_state|. This is called
// when a thread exits.
static void rand_thread_local_state_free(void *state_in) {

  struct rand_thread_local_state *state = state_in;
  if (state_in == NULL) {
    return;
  }

  thread_local_list_delete_node(state);

  // Potentially, something could kill the thread before an entropy source has
  // been associated to the thread-local randomness generator object.
  if (state->entropy_source != NULL) {
    state->entropy_source->methods->free_thread(state->entropy_source);
  }

  OPENSSL_free(state->entropy_source);
  OPENSSL_free(state);
}

// rand_ensure_valid_state determines whether |state| is in a valid state. The
// reasons are documented with inline comments in the function.
//
// Returns 1 if |state| is in a valid state and 0 otherwise.
static int rand_ensure_valid_state(const struct rand_thread_local_state *state) {

  // Currently, the Go based test runner cannot execute a unit test stanza with
  // guaranteed sequential execution. VM UBE testing is using a global file
  // that all unit tests will read if ever taking a path to |RAND_bytes|. The
  // validation below will have a high likelihood of triggering. Disable the
  // validation for VM UBE testing, until Go test runner can guarantee
  // sequential execution.

#if !defined(AWSLC_VM_UBE_TESTING)
  // We do not allow the UBE generation number to change while executing AWS-LC
  // randomness generation code e.g. while |RAND_bytes| executes. One way to hit
  // this error is if snapshotting the address space while executing
  // |RAND_bytes| and while VM UBE is active.
  uint64_t current_generation_number = 0;
  if (CRYPTO_get_ube_generation_number(&current_generation_number) == 1 &&
      current_generation_number != state->generation_number) {
    return 0;
  }
#endif

  return 1;
}

// rand_check_ctr_drbg_uniqueness computes whether |state| must be randomized
// to ensure uniqueness.
//
// Note: If |rand_check_ctr_drbg_uniqueness| returns 0 it does not necessarily
// imply that an UBE occurred. It can also mean that no UBE detection is
// supported or that UBE detection failed. In these cases, |state| must also be
// randomized to ensure uniqueness. Any special future cases can be handled in
// this function.
//
// Return 0 if |state| must be randomized. 1 otherwise.
static int rand_check_ctr_drbg_uniqueness(struct rand_thread_local_state *state) {

  uint64_t current_generation_number = 0;
  if (CRYPTO_get_ube_generation_number(&current_generation_number) != 1) {
    return 0;
  }

  if (current_generation_number != state->generation_number) {
    state->generation_number = current_generation_number;
    return 0;
  }

  return 1;
}

// rand_maybe_get_ctr_drbg_pred_resistance maybe fills |pred_resistance| with
// |RAND_PRED_RESISTANCE_LEN| bytes. The bytes are sourced from the prediction
// resistance source from the entropy source in |state|, if such a source has
// been configured.
//
// |*pred_resistance_len| is set to 0 if no prediction resistance source is
// available and |RAND_PRED_RESISTANCE_LEN| otherwise.
static void rand_maybe_get_ctr_drbg_pred_resistance(
  const struct entropy_source_t *entropy_source,
  uint8_t pred_resistance[RAND_PRED_RESISTANCE_LEN],
  size_t *pred_resistance_len) {

  GUARD_PTR_ABORT(entropy_source);
  GUARD_PTR_ABORT(pred_resistance_len);

  *pred_resistance_len = 0;

  if (entropy_source->methods->get_prediction_resistance != NULL) {
    if (entropy_source->methods->get_prediction_resistance(
      entropy_source, pred_resistance) != 1) {
      abort();
    }
    *pred_resistance_len = RAND_PRED_RESISTANCE_LEN;
  }
}

// rand_get_ctr_drbg_seed_entropy source entropy for seeding and reseeding the
// CTR-DRBG state. Firstly, |seed| is filled with |CTR_DRBG_ENTROPY_LEN| bytes
// from the seed source configured in |entropy_source|. Secondly, if available,
// |CTR_DRBG_ENTROPY_LEN| bytes is filled into |extra_entropy| sourced
// from the extra entropy source configured in |entropy_source|.
//
// |*extra_entropy_len| is set to 0 if no extra entropy source
// is available and |CTR_DRBG_ENTROPY_LEN| otherwise.
static void rand_get_ctr_drbg_seed_entropy(
  const struct entropy_source_t *entropy_source,
  uint8_t seed[CTR_DRBG_ENTROPY_LEN],
  uint8_t extra_entropy[CTR_DRBG_ENTROPY_LEN],
  size_t *extra_entropy_len) {

  GUARD_PTR_ABORT(entropy_source);
  GUARD_PTR_ABORT(extra_entropy_len);

  *extra_entropy_len = 0;

  // If the seed source is missing it is impossible to source any entropy.
  if (entropy_source->methods->get_seed(entropy_source, seed) != 1) {
    abort();
  }

  // Not all entropy source configurations will have a personalization string
  // source. Hence, it's optional. But use it if configured.
  if (entropy_source->methods->get_extra_entropy != NULL) {
    if(entropy_source->methods->get_extra_entropy(
        entropy_source, extra_entropy) != 1) {
      abort();
    }
    *extra_entropy_len = CTR_DRBG_ENTROPY_LEN;
  }
}

// rand_ctr_drbg_reseed reseeds the CTR-DRBG state in |state|.
static void rand_ctr_drbg_reseed(struct rand_thread_local_state *state,
  const uint8_t seed[CTR_DRBG_ENTROPY_LEN],
  const uint8_t additional_data[CTR_DRBG_ENTROPY_LEN],
  size_t additional_data_len) {

  GUARD_PTR_ABORT(state);

  if (CTR_DRBG_reseed(&(state->drbg), seed, additional_data,
        additional_data_len) != 1) {
    abort();
  }

  state->reseed_calls_since_initialization++;
  state->generate_calls_since_seed = 0;
}

// rand_state_initialize initializes the thread-local state |state|. In
// particular initializes the CTR-DRBG state with the initial seed material.
static void rand_state_initialize(struct rand_thread_local_state *state) {

  GUARD_PTR_ABORT(state);

  state->entropy_source = get_entropy_source();
  if (state->entropy_source == NULL) {
    abort();
  }

  uint8_t seed[CTR_DRBG_ENTROPY_LEN];
  uint8_t personalization_string[CTR_DRBG_ENTROPY_LEN];
  size_t personalization_string_len = 0;
  rand_get_ctr_drbg_seed_entropy(state->entropy_source, seed,
    personalization_string, &personalization_string_len);

  assert(personalization_string_len == 0 ||
         personalization_string_len == CTR_DRBG_ENTROPY_LEN);

  if (!CTR_DRBG_init(&(state->drbg), seed, personalization_string,
        personalization_string_len)) {
    abort();
  }

  state->reseed_calls_since_initialization = 0;
  state->generate_calls_since_seed = 0;
  uint64_t current_generation_number = 0;
  if (CRYPTO_get_ube_generation_number(&current_generation_number) != 1) {
    state->generation_number = 0;
  } else {
    state->generation_number = current_generation_number;
  }
  CRYPTO_MUTEX_init(&state->state_clear_lock);

  OPENSSL_cleanse(seed, CTR_DRBG_ENTROPY_LEN);
  OPENSSL_cleanse(personalization_string, CTR_DRBG_ENTROPY_LEN);
}

// RAND_bytes_core generates |out_len| bytes of randomness and puts them in
// |out|. The CTR-DRBG state in |state| is managed to ensure uniqueness and
// usage requirements are met.
//
// The argument |use_user_pred_resistance| must be either
// |RAND_USE_USER_PRED_RESISTANCE| or |RAND_NO_USER_PRED_RESISTANCE|. The former
// cause the content of |user_pred_resistance| to be mixed in as prediction
// resistance. The latter ensures that |user_pred_resistance| is not used.
//
// If the state has just been initialized, then |ctr_drbg_state_is_fresh| is 1.
// Otherwise, 0.
static void rand_bytes_core(
  struct rand_thread_local_state *state,
  uint8_t *out, size_t out_len,
  const uint8_t user_pred_resistance[RAND_PRED_RESISTANCE_LEN],
  int use_user_pred_resistance, int ctr_drbg_state_is_fresh) {

  GUARD_PTR_ABORT(state);
  GUARD_PTR_ABORT(out);

  // must_reseed_before_generate is 1 if we must reseed before invoking the
  // CTR-DRBG generate function CTR_DRBG_generate().
  int must_reseed_before_generate = 0;

  // Ensure that the CTR-DRBG state is unique. If the state is fresh then
  // uniqueness is guaranteed.
  if (rand_check_ctr_drbg_uniqueness(state) != 1 &&
      ctr_drbg_state_is_fresh != 1) {
    must_reseed_before_generate = 1;
  }

  // If a prediction resistance source is available, use it.
  // Prediction resistance is only used on first invocation of the CTR-DRBG,
  // ensuring that its state is randomized before generating output.
  size_t first_pred_resistance_len = 0;
  uint8_t pred_resistance[RAND_PRED_RESISTANCE_LEN] = {0};
  rand_maybe_get_ctr_drbg_pred_resistance(state->entropy_source,
    pred_resistance, &first_pred_resistance_len);

  // If caller input user-controlled prediction resistance, use it.
  if (use_user_pred_resistance == RAND_USE_USER_PRED_RESISTANCE) {
    for (size_t i = 0; i < RAND_PRED_RESISTANCE_LEN; i++) {
      pred_resistance[i] ^= user_pred_resistance[i];
    }
    first_pred_resistance_len = RAND_PRED_RESISTANCE_LEN;
  }

  assert(first_pred_resistance_len == 0 ||
         first_pred_resistance_len == RAND_PRED_RESISTANCE_LEN);

  // Synchronize with |rand_thread_local_state_clear_all|. In case a
  // thread-local state has been zeroized, thread execution will block here
  // because there is no secure way to generate randomness from that state.
  // Note that this lock is thread-local and therefore not contended except at
  // process exit.
  CRYPTO_MUTEX_lock_read(&state->state_clear_lock);

  // Iterate CTR-DRBG generate until |out_len| bytes of randomness have been
  // generated. CTR_DRBG_generate can maximally generate
  // |CTR_DRBG_MAX_GENERATE_LENGTH| bytes per usage of its state see
  // SP800-90A Rev 1 Table 3. If user requests more, we must generate output in
  // chunks and concatenate.
  while (out_len > 0) {
    size_t todo = out_len;

    if (todo > CTR_DRBG_MAX_GENERATE_LENGTH) {
      todo = CTR_DRBG_MAX_GENERATE_LENGTH;
    }

    if (must_reseed_before_generate == 1 ||
       (state->generate_calls_since_seed + 1) > kCtrDrbgReseedInterval) {

      // An unlock-lock cycle is located here to not acquire any locks while we
      // might perform system calls (e.g. when sourcing OS entropy). This
      // shields against known bugs. For example, glibc can implement locks
      // using memory transactions on powerpc that has been observed to break
      // when reaching |getrandom| through |syscall|. For this, see
      // https://github.com/google/boringssl/commit/17ce286e0792fc2855fb7e34a968bed17ae914af
      // https://www.kernel.org/doc/Documentation/powerpc/transactional_memory.txt
      //
      // Even though the unlock-lock cycle is under the loop iteration,
      // practically a request size (i.e. the value of |out_len|), will
      // almost-always be strictly less than |CTR_DRBG_MAX_GENERATE_LENGTH|.
      // Hence, practically, only one lock-unlock rotation will be required.
      CRYPTO_MUTEX_unlock_read(&state->state_clear_lock);
      uint8_t seed[CTR_DRBG_ENTROPY_LEN];
      uint8_t additional_data[CTR_DRBG_ENTROPY_LEN];
      size_t additional_data_len = 0;
      rand_get_ctr_drbg_seed_entropy(state->entropy_source, seed,
        additional_data, &additional_data_len);
      CRYPTO_MUTEX_lock_read(&state->state_clear_lock);

      rand_ctr_drbg_reseed(state, seed, additional_data,
        additional_data_len);
      must_reseed_before_generate = 0;

      OPENSSL_cleanse(seed, CTR_DRBG_ENTROPY_LEN);
      OPENSSL_cleanse(additional_data, CTR_DRBG_ENTROPY_LEN);
    }

    if (!CTR_DRBG_generate(&(state->drbg), out, todo, pred_resistance,
          first_pred_resistance_len)) {
      abort();
    }

    out += todo;
    out_len -= todo;
    state->generate_calls_since_seed++;
    first_pred_resistance_len = 0;
  }

  OPENSSL_cleanse(pred_resistance, RAND_PRED_RESISTANCE_LEN);

  if (rand_ensure_valid_state(state) != 1) {
    abort();
  }

  CRYPTO_MUTEX_unlock_read(&state->state_clear_lock);
}

static void rand_bytes_impl(thread_local_data_t tls_key, uint8_t *out,
  size_t out_len, const uint8_t user_pred_resistance[RAND_PRED_RESISTANCE_LEN],
  int use_user_pred_resistance) {

  if (out_len == 0) {
    return;
  }

  // Lock state here because CTR-DRBG-generate can be invoked multiple times
  // and every successful invocation increments the service indicator.
  FIPS_service_indicator_lock_state();

  struct rand_thread_local_state *state =
      CRYPTO_get_thread_local(tls_key);

  int ctr_drbg_state_is_fresh = 0;

  if (state == NULL) {
    state = OPENSSL_zalloc(sizeof(struct rand_thread_local_state));
    if (state == NULL ||
        CRYPTO_set_thread_local(tls_key, state,
                                   rand_thread_local_state_free) != 1) {
      abort();
    }

    rand_state_initialize(state);
    thread_local_list_add_node(state);

    ctr_drbg_state_is_fresh = 1;
  }

  rand_bytes_core(state, out, out_len, user_pred_resistance,
    use_user_pred_resistance, ctr_drbg_state_is_fresh);

  FIPS_service_indicator_unlock_state();
  FIPS_service_indicator_update_state();
}

int RAND_bytes_with_user_prediction_resistance(uint8_t *out, size_t out_len,
  const uint8_t user_pred_resistance[RAND_PRED_RESISTANCE_LEN]) {

  GUARD_PTR_ABORT(user_pred_resistance);

  rand_bytes_impl(OPENSSL_THREAD_LOCAL_PRIVATE_RAND, out, out_len,
    user_pred_resistance, RAND_USE_USER_PRED_RESISTANCE);
  return 1;
}

int RAND_bytes(uint8_t *out, size_t out_len) {

  static const uint8_t kZeroPredResistance[RAND_PRED_RESISTANCE_LEN] = {0};
  rand_bytes_impl(OPENSSL_THREAD_LOCAL_PRIVATE_RAND, out, out_len,
    kZeroPredResistance, RAND_NO_USER_PRED_RESISTANCE);
  return 1;
}

int RAND_priv_bytes(uint8_t *out, size_t out_len) {
  return RAND_bytes(out, out_len);
}

int RAND_public_bytes(uint8_t *out, size_t out_len) {
  static const uint8_t kZeroPredResistance[RAND_PRED_RESISTANCE_LEN] = {0};
  rand_bytes_impl(OPENSSL_THREAD_LOCAL_PUBLIC_RAND, out, out_len,
    kZeroPredResistance, RAND_NO_USER_PRED_RESISTANCE);
  return 1;
}

int RAND_pseudo_bytes(uint8_t *out, size_t out_len) {
  return RAND_bytes(out, out_len);
}

// Returns the number of generate calls made on the private thread-local state
// since last seed/reseed. Returns 0 if private thread-local state has not been
// initialized.
uint64_t get_private_thread_generate_calls_since_seed(void) {

  struct rand_thread_local_state *state =
      CRYPTO_get_thread_local(OPENSSL_THREAD_LOCAL_PRIVATE_RAND);
  if (state == NULL) {
    return 0;
  }

  return state->generate_calls_since_seed;
}

// Returns the number of reseed calls made on the private thread-local state
// since initialization. Returns 0 if private thread-local state has not been
// initialized.
uint64_t get_private_thread_reseed_calls_since_initialization(void) {

  struct rand_thread_local_state *state =
      CRYPTO_get_thread_local(OPENSSL_THREAD_LOCAL_PRIVATE_RAND);
  if (state == NULL) {
    return 0;
  }

  return state->reseed_calls_since_initialization;
}

// Returns the number of generate calls made on the public thread-local state
// since last seed/reseed. Returns 0 if public thread-local state has not been
// initialized.
uint64_t get_public_thread_generate_calls_since_seed(void) {

  struct rand_thread_local_state *state =
      CRYPTO_get_thread_local(OPENSSL_THREAD_LOCAL_PUBLIC_RAND);
  if (state == NULL) {
    return 0;
  }

  return state->generate_calls_since_seed;
}

// Returns the number of reseed calls made on the public thread-local state
// since initialization. Returns 0 if public thread-local state has not been
// initialized.
uint64_t get_public_thread_reseed_calls_since_initialization(void) {

  struct rand_thread_local_state *state =
      CRYPTO_get_thread_local(OPENSSL_THREAD_LOCAL_PUBLIC_RAND);
  if (state == NULL) {
    return 0;
  }

  return state->reseed_calls_since_initialization;
}
