KeyEncapsulation.cpp

// exports.KeyEncapsulation

#include "KeyEncapsulation.h"

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

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

namespace KeyEncapsulation {

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

  /**
   * Constructs an instance of KeyEncapsulation.
   * @name KeyEncapsulation
   * @class
   * @constructs KeyEncapsulation
   * @param {KEMs.Algorithm} algorithm - The KEM algorithm to use.
   * @param {Buffer} [secretKey] - An optional secret key. If not specified, use KeyEncapsulation#generateKeypair later to create a secret key.
   * @throws {TypeError} Will throw an error if any argument is invalid.
   */
  KeyEncapsulation::KeyEncapsulation(const Napi::CallbackInfo& info) : Napi::ObjectWrap<KeyEncapsulation>(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();
    if (info.Length() >= 2) {
      if (!info[1].IsBuffer()) {
        throw Napi::TypeError::New(env, "Secret key must be a buffer");
      }
      const auto secretKeyBuffer = info[1].As<Napi::Buffer<byte>>();
      const auto secretKeyData = secretKeyBuffer.Data();
      const bytes secretKeyVec(secretKeyData, secretKeyData + secretKeyBuffer.Length());
      try {
        oqsKE = std::make_unique<oqs::KeyEncapsulation>(algorithm, secretKeyVec);
      } catch (const std::exception& ex) {
        throw Napi::TypeError::New(env, ex.what());
      }
    } else {
      try {
        oqsKE = std::make_unique<oqs::KeyEncapsulation>(algorithm);
      } catch (const std::exception& ex) {
        throw Napi::TypeError::New(env, ex.what());
      }
    }
  }

  /**
   * Gets the details for the KEM algorithm that the instance was constructed with.
   * @memberof KeyEncapsulation
   * @instance
   * @method
   * @name getDetails
   * @returns {Object} - An object containing the details of the KEM algorithm.
   */
  Napi::Value KeyEncapsulation::getDetails(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    const oqs::KeyEncapsulation::KeyEncapsulationDetails details = oqsKE->get_details();
    auto detailsObj = Napi::Object::New(env);
    detailsObj.Set(
      Napi::String::New(env, "name"),
      Napi::String::New(env, details.name)
    );
    detailsObj.Set(
      Napi::String::New(env, "version"),
      Napi::String::New(env, details.version)
    );
    detailsObj.Set(
      Napi::String::New(env, "claimedNistLevel"),
      Napi::Number::New(env, details.claimed_nist_level)
    );
    detailsObj.Set(
      Napi::String::New(env, "isINDCCA"),
      Napi::Boolean::New(env, details.is_ind_cca)
    );
    detailsObj.Set(
      Napi::String::New(env, "publicKeyLength"),
      Napi::Number::New(env, details.length_public_key)
    );
    detailsObj.Set(
      Napi::String::New(env, "secretKeyLength"),
      Napi::Number::New(env, details.length_secret_key)
    );
    detailsObj.Set(
      Napi::String::New(env, "ciphertextLength"),
      Napi::Number::New(env, details.length_ciphertext)
    );
    detailsObj.Set(
      Napi::String::New(env, "sharedSecretLength"),
      Napi::Number::New(env, details.length_shared_secret)
    );
    return detailsObj;
  }

  /**
   * Generates a keypair. Overwrites any existing secret key on the instance with the generated secret key.
   * @memberof KeyEncapsulation
   * @instance
   * @method
   * @name generateKeypair
   * @returns {Buffer} - A Buffer containing the public key.
   * @throws {Error} Will throw an error if memory cannot be allocated..
   */
  Napi::Value KeyEncapsulation::generateKeypair(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    try {
      const bytes publicKeyVec = oqsKE->generate_keypair();
      bytes* publicKeyVecCopy = new (std::nothrow) bytes(publicKeyVec);
      if (publicKeyVecCopy == nullptr) {
        throw Napi::Error::New(env, "Failed to allocate memory");
      }
      Napi::MemoryManagement::AdjustExternalMemory(env, publicKeyVecCopy->size());
      return Napi::Buffer<byte>::New(
        env,
        publicKeyVecCopy->data(),
        publicKeyVecCopy->size(),
        [](Napi::Env cbEnv, byte* /* unused */, bytes* vec) -> void {
          if (vec != nullptr) {
            Napi::MemoryManagement::AdjustExternalMemory(cbEnv, -vec->size());
            // No need to secure free public key
          }
          delete vec;
        },
        publicKeyVecCopy
      );
    } catch (const std::exception& ex) {
      throw Napi::Error::New(env, ex.what());
    }
  }

  /**
   * Exports the secret key.
   * @memberof KeyEncapsulation
   * @instance
   * @method
   * @name exportSecretKey
   * @returns {Buffer} - A Buffer containing the secret key.
   * @throws {Error} Will throw an error if memory cannot be allocated.
   */
  Napi::Value KeyEncapsulation::exportSecretKey(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    bytes secretKeyVec = oqsKE->export_secret_key();
    bytes* secretKeyVecCopy = new (std::nothrow) bytes(secretKeyVec);
    oqs::mem_cleanse(secretKeyVec);
    if (secretKeyVecCopy == nullptr) {
      throw Napi::Error::New(env, "Failed to allocate memory");
    }
    Napi::MemoryManagement::AdjustExternalMemory(env, secretKeyVecCopy->size());
    return Napi::Buffer<byte>::New(
      env,
      secretKeyVecCopy->data(),
      secretKeyVecCopy->size(),
      [](Napi::Env cbEnv, byte* /* unused */, bytes* vec) -> void {
        if (vec != nullptr) {
          Napi::MemoryManagement::AdjustExternalMemory(cbEnv, -vec->size());
          oqs::mem_cleanse(*vec);
        }
        delete vec;
      },
      secretKeyVecCopy
    );
  }

  /**
   * An object with the following properties:
   * * `ciphertext`: The ciphertext to be given to the owner of the public key.
   * * `sharedSecret`: The shared secret.
   * @memberof KeyEncapsulation
   * @typedef {Object} CiphertextSharedSecretPair
   */

  /**
   * Encapsulates the shared secret using a provided public key.
   * @memberof KeyEncapsulation
   * @instance
   * @method
   * @name encapsulateSecret
   * @param {Buffer} publicKey - The public key belonging to the intended recipient of the shared secret.
   * @returns {KeyEncapsulation.CiphertextSharedSecretPair} - The ciphertext and shared secret.
   * @throws {TypeError} Will throw an error if any argument is invalid.
   * @throws {Error} Will throw an error if memory cannot be allocated.
   */
  Napi::Value KeyEncapsulation::encapsulateSecret(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (info.Length() < 1) {
      throw Napi::TypeError::New(env, "Public key must be a buffer");
    }
    if (!info[0].IsBuffer()) {
      throw Napi::TypeError::New(env, "Public key must be a buffer");
    }
    const auto publicKeyBuffer = info[0].As<Napi::Buffer<byte>>();
    const auto publicKeyData = publicKeyBuffer.Data();
    const bytes publicKeyVec(publicKeyData, publicKeyData + publicKeyBuffer.Length());
    try {
      std::pair<bytes, bytes> encapPair = oqsKE->encap_secret(publicKeyVec);
      bytes* ciphertextVec = new (std::nothrow) bytes(encapPair.first);
      bytes* sharedSecretVec = new (std::nothrow) bytes(encapPair.second);
      // Secure free shared secret returned by OQS
      oqs::mem_cleanse(encapPair.second);
      if (ciphertextVec == nullptr || sharedSecretVec == nullptr) {
        throw Napi::Error::New(env, "Failed to allocate memory");
      }
      Napi::MemoryManagement::AdjustExternalMemory(env, ciphertextVec->size());
      Napi::MemoryManagement::AdjustExternalMemory(env, sharedSecretVec->size());
      auto ciphertextSharedSecretPair = Napi::Object::New(env);
      ciphertextSharedSecretPair.Set(
        Napi::String::New(env, "ciphertext"),
        Napi::Buffer<byte>::New(
          env,
          ciphertextVec->data(),
          ciphertextVec->size(),
          [](Napi::Env cbEnv, byte* /* unused */, bytes* vec) -> void {
            if (vec != nullptr) {
              Napi::MemoryManagement::AdjustExternalMemory(cbEnv, -vec->size());
              // No need to secure free ciphertext
            }
            delete vec;
          },
          ciphertextVec
        )
      );
      ciphertextSharedSecretPair.Set(
        Napi::String::New(env, "sharedSecret"),
        Napi::Buffer<byte>::New(
          env,
          sharedSecretVec->data(),
          sharedSecretVec->size(),
          [](Napi::Env cbEnv, byte* /* unused */, bytes* vec) -> void {
            if (vec != nullptr) {
              Napi::MemoryManagement::AdjustExternalMemory(cbEnv, -vec->size());
              oqs::mem_cleanse(*vec);
            }
            delete vec;
          },
          sharedSecretVec
        )
      );
      return ciphertextSharedSecretPair;
    } catch (const std::exception& ex) {
      throw Napi::TypeError::New(env, ex.what());
    }
  }

  /**
   * Decapsulates the shared secret using a provided public key.
   * @memberof KeyEncapsulation
   * @instance
   * @method
   * @name decapsulateSecret
   * @param {Buffer} ciphertext - The ciphertext that was encrypted using the instance's public key.
   * @returns {Buffer} - The shared secret.
   * @throws {TypeError} Will throw an error if any argument is invalid.
   * @throws {Error} Will throw an error if memory cannot be allocated.
   */
  Napi::Value KeyEncapsulation::decapsulateSecret(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (info.Length() < 1) {
      throw Napi::TypeError::New(env, "Ciphertext must be a buffer");
    }
    if (!info[0].IsBuffer()) {
      throw Napi::TypeError::New(env, "Ciphertext must be a buffer");
    }
    const auto ciphertextBuffer = info[0].As<Napi::Buffer<byte>>();
    const auto ciphertextData = ciphertextBuffer.Data();
    const bytes ciphertextVec(ciphertextData, ciphertextData + ciphertextBuffer.Length());
    try {
      bytes sharedSecretVec = oqsKE->decap_secret(ciphertextVec);
      bytes* sharedSecretVecCopy = new (std::nothrow) bytes(sharedSecretVec);
      // Secure free shared secret returned by OQS
      oqs::mem_cleanse(sharedSecretVec);
      if (sharedSecretVecCopy == nullptr) {
        throw Napi::Error::New(env, "Failed to allocate memory");
      }
      Napi::MemoryManagement::AdjustExternalMemory(env, sharedSecretVecCopy->size());
      return Napi::Buffer<byte>::New(
        env,
        sharedSecretVecCopy->data(),
        sharedSecretVecCopy->size(),
        [](Napi::Env cbEnv, byte* /* unused */, bytes* vec) -> void {
          if (vec != nullptr) {
            Napi::MemoryManagement::AdjustExternalMemory(cbEnv, -vec->size());
            oqs::mem_cleanse(*vec);
          }
          delete vec;
        },
        sharedSecretVecCopy
      );
    } catch (const std::exception& ex) {
      throw Napi::TypeError::New(env, ex.what());
    }
  }

  void KeyEncapsulation::Init(Napi::Env env, Napi::Object exports) {
    Napi::Function func = DefineClass(env, "KeyEncapsulation", {
      InstanceMethod<&KeyEncapsulation::getDetails>("getDetails"),
      InstanceMethod<&KeyEncapsulation::generateKeypair>("generateKeypair"),
      InstanceMethod<&KeyEncapsulation::exportSecretKey>("exportSecretKey"),
      InstanceMethod<&KeyEncapsulation::encapsulateSecret>("encapsulateSecret"),
      InstanceMethod<&KeyEncapsulation::decapsulateSecret>("decapsulateSecret")
    });
    Napi::FunctionReference* constructor = new Napi::FunctionReference();
    *constructor = Napi::Persistent(func);
    exports.Set(
      Napi::String::New(env, "KeyEncapsulation"),
      func
    );
    env.SetInstanceData<Napi::FunctionReference>(constructor);
  }

  void Init(Napi::Env env, Napi::Object exports) {
    KeyEncapsulation::Init(env, exports);
  }

} // namespace KeyEncapsulation