Random.cpp

// exports.Random

#include "Random.h"

#include <cstdint>
#include <new>
#include <vector>
#include <napi.h>

// liboqs-cpp
#include "rand/rand.h"
#include "common.h"

/** @namespace Random */
namespace Random {

  using oqs::byte;
  using oqs::bytes;

  /**
   * The different PRNG algorithms that can be used. It can be one of the following:
   * * `system`: System PRNG. Reads directly from `/dev/urandom`.
   * * `NIST-KAT`: NIST deterministic RNG for KATs.
   * * `OpenSSL`: OpenSSL's PRNG.
   * Defaults to `system`.
   * @memberof Random
   * @typedef {string} Algorithm
   */

  /**
   * Switches the PRNG algorithm used by the library.
   * @memberof Random
   * @name switchAlgorithm
   * @static
   * @method
   * @param {Random.Algorithm} algorithm - The PRNG algorithm to use.
   * @throws {TypeError} Will throw an error if any argument is invalid.
   */
  Napi::Value switchAlgorithm(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (info.Length() < 1) {
      throw Napi::TypeError::New(env, "Algorithm must be a string");
    }
    if (!info[0].IsString()) {
      throw Napi::TypeError::New(env, "Algorithm must be a string");
    }
    const auto algorithm = info[0].As<Napi::String>().Utf8Value();
    try {
      oqs::rand::randombytes_switch_algorithm(algorithm);
    } catch (const std::exception& ex) {
      throw Napi::TypeError::New(env, ex.what());
    }
    return env.Undefined();
  }

  /**
   * Generates cryptographically-secure random bytes.
   * @memberof Random
   * @name randomBytes
   * @static
   * @method
   * @param {number} size - The size of the returned Buffer.
   * @returns {Buffer} bytes - A Buffer with `size` random bytes.
   * @throws {TypeError} Will throw an error if any argument is invalid.
   * @throws {Error} Will throw an error if memory cannot be allocated.
   */
  Napi::Value randomBytes(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (info.Length() < 1) {
      throw Napi::TypeError::New(env, "Bytes must be a number");
    }
    if (!info[0].IsNumber()) {
      throw Napi::TypeError::New(env, "Bytes must be a number");
    }
    const auto size = info[0].As<Napi::Number>().Int64Value();
    if (size < 0) {
      throw Napi::TypeError::New(env, "Bytes must be non-negative");
    }
    if (static_cast<std::uint64_t>(size) > SIZE_MAX) {
      throw Napi::TypeError::New(env, "Bytes exceeds the maximum number of bytes that can be generated");
    }
    bytes* randBytes = new (std::nothrow) bytes(size);
    if (randBytes == nullptr) {
      throw Napi::Error::New(env, "Failed to allocate memory");
    }
    oqs::rand::randombytes(*randBytes, static_cast<std::size_t>(size));
    Napi::MemoryManagement::AdjustExternalMemory(env, size);
    return Napi::Buffer<byte>::New(
      env,
      randBytes->data(),
      size,
      [](Napi::Env cbEnv, byte* /* unused */, bytes* vec) -> void {
        if (vec != nullptr) {
          Napi::MemoryManagement::AdjustExternalMemory(cbEnv, -vec->size());
          oqs::mem_cleanse(*vec);
        }
        delete vec;
      },
      randBytes
    );
  }

  /**
   * Generates cryptographically-secure random bytes.
   * @memberof Random
   * @name initNistKat
   * @static
   * @method
   * @param {Buffer} entropy - The entropy input seed. Must be exactly 48 bytes long.
   * @param {Buffer} [personalizationString] - A personalization string. Must be at least 48 bytes long if provided.
   * @throws {TypeError} Will throw an error if any argument is invalid.
   */
  Napi::Value initNistKat(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (info.Length() < 1) {
      throw Napi::TypeError::New(env, "Entropy must be a Buffer");
    }
    if (!info[0].IsBuffer()) {
      throw Napi::TypeError::New(env, "Entropy must be a Buffer");
    }
    // Buffer lengths are checked by oqs::rand::randombytes_nist_kat_init_256bit
    const auto entropyBuffer = info[0].As<Napi::Buffer<byte>>();
    const auto entropyData = entropyBuffer.Data();
    bytes entropyVec(entropyData, entropyData + entropyBuffer.Length());
    if (info.Length() >= 2) {
      if (!info[1].IsBuffer()) {
        throw Napi::TypeError::New(env, "Personalization string must be a Buffer");
      }
      const auto pstringBuffer = info[1].As<Napi::Buffer<byte>>();
      const auto pstringData = pstringBuffer.Data();
      bytes pstringVec(pstringData, pstringData + pstringBuffer.Length());
      try {
        oqs::rand::randombytes_nist_kat_init_256bit(entropyVec, pstringVec);
      } catch (const std::exception& ex) {
        oqs::mem_cleanse(entropyVec);
        oqs::mem_cleanse(pstringVec);
        throw Napi::TypeError::New(env, ex.what());
      }
      oqs::mem_cleanse(pstringVec);
    } else {
      try {
        oqs::rand::randombytes_nist_kat_init_256bit(entropyVec);
      } catch (const std::exception& ex) {
        oqs::mem_cleanse(entropyVec);
        throw Napi::TypeError::New(env, ex.what());
      }
    }
    oqs::mem_cleanse(entropyVec);
    return env.Undefined();
  }

  void Init(Napi::Env env, Napi::Object exports) {
    auto randExports = Napi::Object::New(env);
    randExports.Set(
      Napi::String::New(env, "switchAlgorithm"),
      Napi::Function::New(env, switchAlgorithm)
    );
    randExports.Set(
      Napi::String::New(env, "randomBytes"),
      Napi::Function::New(env, randomBytes)
    );
    randExports.Set(
      Napi::String::New(env, "initNistKat"),
      Napi::Function::New(env, initNistKat)
    );
    exports.Set(
      Napi::String::New(env, "Random"),
      randExports
    );
  }

} // namespace Random