/* Copyright 2020 The BoringSSL Authors
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */

#include <openssl/ctrdrbg.h>

#include "../bcm_support.h"
#include "../fipsmodule/bcm_interface.h"
#include "../internal.h"

#if defined(BORINGSSL_FIPS)

#include <atomic>

// passive_get_seed_entropy writes |out_entropy_len| bytes of entropy, suitable
// for seeding a DRBG, to |out_entropy|. It sets |*out_used_cpu| to one if the
// entropy came directly from the CPU and zero if it came from the OS. It
// actively obtains entropy from the CPU/OS
static void passive_get_seed_entropy(uint8_t *out_entropy,
                                     size_t out_entropy_len,
                                     int *out_want_additional_input) {
  *out_want_additional_input = 0;
  if (bcm_success(BCM_rand_bytes_hwrng(out_entropy, out_entropy_len))) {
    *out_want_additional_input = 1;
  } else {
    CRYPTO_sysrand_for_seed(out_entropy, out_entropy_len);
  }
}

#define ENTROPY_READ_LEN \
  (/* last_block size */ 16 + CTR_DRBG_ENTROPY_LEN * BORINGSSL_FIPS_OVERREAD)

#if defined(OPENSSL_ANDROID)

#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>

// socket_history_t enumerates whether the entropy daemon should be contacted
// for a given entropy request. Values other than socket_not_yet_attempted are
// sticky so if the first attempt to read from the daemon fails it's assumed
// that the daemon is not present and no more attempts will be made. If the
// first attempt is successful then attempts will be made forever more.
enum class socket_history_t {
  // initial value, no connections to the entropy daemon have been made yet.
  socket_not_yet_attempted = 0,
  // reading from the entropy daemon was successful
  socket_success,
  // reading from the entropy daemon failed.
  socket_failed,
};

static std::atomic<socket_history_t> g_socket_history{
    socket_history_t::socket_not_yet_attempted};

// DAEMON_RESPONSE_LEN is the number of bytes that the entropy daemon replies
// with.
#define DAEMON_RESPONSE_LEN 496

static_assert(ENTROPY_READ_LEN == DAEMON_RESPONSE_LEN,
              "entropy daemon response length mismatch");

static int get_seed_from_daemon(uint8_t *out_entropy, size_t out_entropy_len) {
  // |RAND_need_entropy| should never call this function for more than
  // |DAEMON_RESPONSE_LEN| bytes.
  if (out_entropy_len > DAEMON_RESPONSE_LEN) {
    abort();
  }

  const socket_history_t socket_history =
      g_socket_history.load(std::memory_order_acquire);
  if (socket_history == socket_history_t::socket_failed) {
    return 0;
  }

  int ret = 0;
  static const char kSocketPath[] = "/dev/socket/prng_seeder";
  struct sockaddr_un sun;
  uint8_t buffer[DAEMON_RESPONSE_LEN];
  size_t done = 0;
  const int sock = socket(AF_UNIX, SOCK_STREAM, 0);
  if (sock < 0) {
    goto out;
  }

  memset(&sun, 0, sizeof(sun));
  sun.sun_family = AF_UNIX;
  static_assert(sizeof(kSocketPath) <= UNIX_PATH_MAX, "kSocketPath too long");
  OPENSSL_memcpy(sun.sun_path, kSocketPath, sizeof(kSocketPath));

  if (connect(sock, (struct sockaddr *)&sun, sizeof(sun))) {
    goto out;
  }

  while (done < sizeof(buffer)) {
    ssize_t n;
    do {
      n = read(sock, buffer + done, sizeof(buffer) - done);
    } while (n == -1 && errno == EINTR);

    if (n < 1) {
      goto out;
    }
    done += n;
  }

  if (done != DAEMON_RESPONSE_LEN) {
    // The daemon should always write |DAEMON_RESPONSE_LEN| bytes on every
    // connection.
    goto out;
  }

  assert(out_entropy_len <= DAEMON_RESPONSE_LEN);
  OPENSSL_memcpy(out_entropy, buffer, out_entropy_len);
  ret = 1;

out:
  if (socket_history == socket_history_t::socket_not_yet_attempted) {
    socket_history_t expected = socket_history_t::socket_not_yet_attempted;
    // If another thread has already updated |g_socket_history| then we defer
    // to their value.
    g_socket_history.compare_exchange_strong(
        expected,
        (ret == 0) ? socket_history_t::socket_failed
                   : socket_history_t::socket_success,
        std::memory_order_release, std::memory_order_relaxed);
  }

  close(sock);
  return ret;
}

#else

static int get_seed_from_daemon(uint8_t *out_entropy, size_t out_entropy_len) {
  return 0;
}

#endif  // OPENSSL_ANDROID

// RAND_need_entropy is called by the FIPS module when it has blocked because of
// a lack of entropy. This signal is used as an indication to feed it more.
void RAND_need_entropy(size_t bytes_needed) {
  uint8_t buf[ENTROPY_READ_LEN];
  size_t todo = sizeof(buf);
  if (todo > bytes_needed) {
    todo = bytes_needed;
  }

  int want_additional_input;
  if (get_seed_from_daemon(buf, todo)) {
    want_additional_input = 1;
  } else {
    passive_get_seed_entropy(buf, todo, &want_additional_input);
  }

  if (boringssl_fips_break_test("CRNG")) {
    // This breaks the "continuous random number generator test" defined in FIPS
    // 140-2, section 4.9.2, and implemented in |rand_get_seed|.
    OPENSSL_memset(buf, 0, todo);
  }

  BCM_rand_load_entropy(buf, todo, want_additional_input);
}

#endif  // FIPS
