/***
pkey module for lua-openssl binding
@module pkey
@usage
  pkey = require'openssl'.pkey
*/
#include "openssl.h"
#include "private.h"
#include <openssl/rsa.h>
#include <openssl/dh.h>
#include <openssl/dsa.h>
#include <openssl/engine.h>

#if defined(OPENSSL_SUPPORT_SM2)
#ifndef SM2_DEFAULT_USERID
#  define SM2_DEFAULT_USERID "1234567812345678"
#endif
#endif

#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
#define EVP_CIPHER_CTX_reset EVP_CIPHER_CTX_init
#endif

int openssl_pkey_is_private(EVP_PKEY* pkey)
{
  int ret = 1;
  int typ;
  assert(pkey != NULL);
  typ = EVP_PKEY_type(EVP_PKEY_id(pkey));
  switch (typ)
  {
#ifndef OPENSSL_NO_RSA
  case EVP_PKEY_RSA:
  {
    RSA *rsa = EVP_PKEY_get0_RSA(pkey);
    const BIGNUM *d = NULL;

    RSA_get0_key(rsa, NULL, NULL, &d);
    ret = d != NULL;
    break;
  }
#endif
#ifndef OPENSSL_NO_DSA
  case EVP_PKEY_DSA:
  {
    DSA *dsa = EVP_PKEY_get0_DSA(pkey);
    const BIGNUM *p = NULL;
    DSA_get0_key(dsa, NULL, &p);
    ret = p != NULL;
    break;
  }
#endif
#ifndef OPENSSL_NO_DH
  case EVP_PKEY_DH:
  {
    DH *dh = EVP_PKEY_get0_DH(pkey);
    const BIGNUM *p = NULL;
    DH_get0_key(dh, NULL, &p);
    ret = p != NULL;
    break;
  }
#endif
#ifndef OPENSSL_NO_EC
  case EVP_PKEY_EC:
  {
    EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey);
    const BIGNUM *p = EC_KEY_get0_private_key(ec);
    ret = p != NULL;
    break;
  }
#endif
  default:
    ret = 0;
    break;
  }
  return ret;
}

/***
read public/private key from data
@function read
@tparam string|openssl.bio input string data or bio object
@tparam[opt=false] boolean priv prikey set true when input is private key
@tparam[opt='auto'] string format or encoding of input, support 'auto','pem','der'
@tparam[opt] string passhprase when input is private key, or key types 'ec','rsa','dsa','dh'
@treturn evp_pkey public key
@see evp_pkey
*/
static int openssl_pkey_read(lua_State*L)
{
  EVP_PKEY * key = NULL;
  BIO* in = load_bio_object(L, 1);
  int priv = lua_isnone(L, 2) ? 0 : auxiliar_checkboolean(L, 2);
  int fmt = luaL_checkoption(L, 3, "auto", format);
  const char* passphrase = luaL_optstring(L, 4, NULL);
  int type = -1;
  if (passphrase)
  {
    if (strcmp(passphrase, "rsa") == 0 || strcmp(passphrase, "RSA") == 0)
      type = EVP_PKEY_RSA;
    else if (strcmp(passphrase, "dsa") == 0 || strcmp(passphrase, "DSA") == 0)
      type = EVP_PKEY_DSA;
    else if (strcmp(passphrase, "ec") == 0 || strcmp(passphrase, "EC") == 0)
      type = EVP_PKEY_EC;
    else if (strcmp(passphrase, "dh") == 0 || strcmp(passphrase, "DH") == 0)
      type = EVP_PKEY_DH;
  }

  if (fmt == FORMAT_AUTO)
  {
    fmt = bio_is_der(in) ? FORMAT_DER : FORMAT_PEM;
  }

  if (!priv)
  {
    if (fmt == FORMAT_PEM)
    {
      switch (type)
      {
      case EVP_PKEY_RSA:
      {
        RSA* rsa = PEM_read_bio_RSAPublicKey(in, NULL, NULL, NULL);
        if (rsa)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_RSA(key, rsa);
        }
        break;
      }
      case EVP_PKEY_DSA:
      {
        DSA* dsa = PEM_read_bio_DSA_PUBKEY(in, NULL, NULL, NULL);
        if (dsa)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_DSA(key, dsa);
        }
        break;
      }
      case EVP_PKEY_DH:
      {
        DH *dh = PEM_read_bio_DHparams(in, NULL, NULL, NULL);
        if (dh)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_DH(key, dh);
        }
        break;
      }
      case EVP_PKEY_EC:
      {
        EC_KEY *ec = PEM_read_bio_EC_PUBKEY(in, NULL, NULL, NULL);
        if (ec)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_EC_KEY(key, ec);
        }
        break;
      }
      default:
      {
        key = PEM_read_bio_PUBKEY(in, NULL, NULL, NULL);
        break;
      }
      }
      (void)BIO_reset(in);
    }
    else if (fmt == FORMAT_DER)
    {
      switch (type)
      {
      case EVP_PKEY_RSA:
      {
        RSA *rsa = d2i_RSAPublicKey_bio(in, NULL);
        if (rsa)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_RSA(key, rsa);
        }
        break;
      }
      case EVP_PKEY_DSA:
      {
        DSA *dsa = d2i_DSA_PUBKEY_bio(in, NULL);
        if (dsa)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_DSA(key, dsa);
        }
        break;
      }
      case EVP_PKEY_DH:
      {
        DH *dh = d2i_DHparams_bio(in, NULL);
        if (dh)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_DH(key, dh);
        }
        break;
      }
      case EVP_PKEY_EC:
      {
        EC_KEY *ec = d2i_EC_PUBKEY_bio(in, NULL);
        if (ec)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_EC_KEY(key, ec);
        }
        break;
      }
      default:
        key = d2i_PUBKEY_bio(in, NULL);
        break;
      }
      (void)BIO_reset(in);
    }
  }
  else
  {
    if (fmt == FORMAT_PEM)
    {
      key = PEM_read_bio_PrivateKey(in, NULL, NULL, (void*)passphrase);
      (void)BIO_reset(in);
    }
    else if (fmt == FORMAT_DER)
    {
      switch (type)
      {
      case EVP_PKEY_RSA:
      {
        RSA *rsa = d2i_RSAPrivateKey_bio(in, NULL);
        if (rsa)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_RSA(key, rsa);
        }
        break;
      }
      case EVP_PKEY_DSA:
      {
        DSA *dsa = d2i_DSAPrivateKey_bio(in, NULL);
        if (dsa)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_DSA(key, dsa);
        }
        break;
      }
      case EVP_PKEY_DH:
      {
        DH *dh = d2i_DHparams_bio(in, NULL);
        if (dh)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_DH(key, dh);
        }
      }
      case EVP_PKEY_EC:
      {
        EC_KEY *ec = d2i_ECPrivateKey_bio(in, NULL);
        if (ec)
        {
          key = EVP_PKEY_new();
          EVP_PKEY_assign_EC_KEY(key, ec);
        }
        break;
      }
      default:
      {
        if (passphrase)
          key = d2i_PKCS8PrivateKey_bio(in, NULL, NULL, (void*)passphrase);
        else
          key = d2i_PrivateKey_bio(in, NULL);
        break;
      }
      }
      (void)BIO_reset(in);
    }
  }
  BIO_free(in);
  if (key)
  {
    PUSH_OBJECT(key, "openssl.evp_pkey");
    return 1;
  }
  return openssl_pushresult(L, 0);
}

static int EC_KEY_generate_key_part(EC_KEY *eckey)
{
  int ok = 0;
  BN_CTX  *ctx = NULL;
  BIGNUM  *priv_key = NULL, *order = NULL;
  EC_POINT *pub_key = NULL;
  const EC_GROUP *group;

  if (!eckey)
  {
    return 0;
  }
  group = EC_KEY_get0_group(eckey);

  if ((order = BN_new()) == NULL) goto err;
  if ((ctx = BN_CTX_new()) == NULL) goto err;
  priv_key = (BIGNUM*)EC_KEY_get0_private_key(eckey);

  if (priv_key == NULL)
  {
    goto err;
  }

  if (!EC_GROUP_get_order(group, order, ctx))
    goto err;

  if (BN_is_zero(priv_key))
    goto err;
  pub_key = (EC_POINT *)EC_KEY_get0_public_key(eckey);

  if (pub_key == NULL)
  {
    pub_key = EC_POINT_new(group);
    if (pub_key == NULL)
      goto err;
    EC_KEY_set_public_key(eckey, pub_key);
    EC_POINT_free(pub_key);
    pub_key = (EC_POINT *)EC_KEY_get0_public_key(eckey);
  }

  if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx))
    goto err;
  EC_POINT_make_affine(EC_KEY_get0_group(eckey),
                       pub_key,
                       NULL);
  ok = 1;

err:
  if (order)
    BN_free(order);

  if (ctx != NULL)
    BN_CTX_free(ctx);
  return (ok);
}


#define EC_GET_FIELD(_name)        {                                                  \
  lua_getfield(L, -1, #_name);                                                        \
  if (lua_isstring(L, -1)) {                                                          \
    size_t l = 0; const char* bn = luaL_checklstring(L, -1, &l);                      \
    if (_name == NULL)  _name = BN_new();                                             \
    BN_bin2bn((const unsigned char *)bn, l, _name);                                   \
  } else if (auxiliar_getclassudata(L, "openssl.bn", -1)) {                                 \
    const BIGNUM* bn = CHECK_OBJECT(-1, BIGNUM, "openssl.bn");                        \
    if (_name == NULL)  _name = BN_new();                                             \
    BN_copy(_name, bn);                                                               \
  } else if (!lua_isnil(L, -1))                                                       \
    luaL_error(L, "parameters must have \"%s\" field string or openssl.bn", #_name);  \
  lua_pop(L, 1);                                                                      \
}

/***
generate a new ec keypair
@function new
@tparam string alg, alg must be 'ec'
@tparam string|number curvename this can be integer as curvename NID
@tparam[opt] integer flags when alg is ec need this.
@treturn evp_pkey object with mapping to EVP_PKEY in openssl
*/
/***
generate a new keypair
@function new
@tparam[opt='rsa'] string alg, accept `rsa`,`dsa`,`dh`
@tparam[opt=2048|512] integer bits, `rsa` with 2048, `dh` or `dsa` with 1024
@tparam[opt] integer e, when alg is `rsa` give e value default is 0x10001,
 when alg is `dh` give generator value default is 2,
 when alg is `dsa` give string type seed value default is none.
@tparam[opt] engine eng
@treturn evp_pkey object with mapping to EVP_PKEY in openssl
*/
/***
create a new keypair by factors of keypair or get public key only
@function new
@tparam table factors to create private/public key, key alg only accept accept 'rsa','dsa','dh','ec' and must exist</br>
 when arg is rsa, table may with key n,e,d,p,q,dmp1,dmq1,iqmp, both are binary string or openssl.bn<br>
 when arg is dsa, table may with key p,q,g,priv_key,pub_key, both are binary string or openssl.bn<br>
 when arg is dh, table may with key p,g,priv_key,pub_key, both are binary string or openssl.bn<br>
 when arg is ec, table may with D,X,Y,Z,both are binary string or openssl.bn<br>
@treturn evp_pkey object with mapping to EVP_PKEY in openssl
@usage
 --create rsa public key
   pubkey = new({alg='rsa',n=...,e=...}
 --create new rsa
   rsa = new({alg='rsa',n=...,q=...,e=...,...}
*/
static LUA_FUNCTION(openssl_pkey_new)
{
  EVP_PKEY *pkey = NULL;
  const char* alg = "rsa";

  if (lua_isnoneornil(L, 1) || lua_isstring(L, 1))
  {
    alg = luaL_optstring(L, 1, alg);
    if (strcasecmp(alg, "rsa") == 0)
    {
      int bits = luaL_optint(L, 2, 2048);
      int e = luaL_optint(L, 3, 65537);
      ENGINE *eng = lua_isnoneornil(L, 4) ? NULL : CHECK_OBJECT(4, ENGINE, "openssl.engine");
      BIGNUM *E = BN_new();
      BN_set_word(E, e);

      RSA *rsa = eng ? RSA_new_method(eng) : RSA_new();
      if (RSA_generate_key_ex(rsa, bits, E, NULL))
      {
        pkey = EVP_PKEY_new();
        EVP_PKEY_assign_RSA(pkey, rsa);
      }
      else
        RSA_free(rsa);

      BN_free(E);
    }
    else if (strcasecmp(alg, "dsa") == 0)
    {
      int bits = luaL_optint(L, 2, 1024);
      size_t seed_len = 0;
      const char* seed = luaL_optlstring(L, 3, NULL, &seed_len);
      ENGINE *eng = lua_isnoneornil(L, 4) ? NULL : CHECK_OBJECT(4, ENGINE, "openssl.engine");

      DSA *dsa = eng ? DSA_new_method(eng) : DSA_new();
      if (DSA_generate_parameters_ex(dsa, bits, (byte*)seed, seed_len, NULL, NULL, NULL)
          && DSA_generate_key(dsa))
      {
        pkey = EVP_PKEY_new();
        EVP_PKEY_assign_DSA(pkey, dsa);
      }
      else
        DSA_free(dsa);
    }
    else if (strcasecmp(alg, "dh") == 0)
    {
      int bits = luaL_optint(L, 2, 1024);
      int generator = luaL_optint(L, 3, 2);
      ENGINE *eng = lua_isnoneornil(L, 4) ? NULL : CHECK_OBJECT(4, ENGINE, "openssl.engine");

#if (OPENSSL_VERSION_NUMBER >= 0x30000000L)
      EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, eng);
      if (ctx)
      {
        int ret = EVP_PKEY_paramgen_init(ctx);
        if (ret == 1)
        {
          ret = EVP_PKEY_keygen_init(ctx);
          if ( ret == 1)
          {
            ret = EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, bits);
            if ( ret == 1)
            {
              ret = EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, generator);
              if(ret == 1)
              {
                ret = EVP_PKEY_keygen(ctx, &pkey);
                if (ret == 1)
                  EVP_PKEY_set_type(pkey, EVP_PKEY_DH);
              }
            }
          }
        }
        EVP_PKEY_CTX_free(ctx);
      }
#else
      DH* dh = eng ? DH_new_method(eng) : DH_new();
      if (DH_generate_parameters_ex(dh, bits, generator, NULL))
      {
        if (DH_generate_key(dh))
        {
          pkey = EVP_PKEY_new();
          //TODO: remove up_ref
          DH_up_ref(dh);
          EVP_PKEY_assign_DH(pkey, dh);
        }
        else
          DH_free(dh);
      }
      else
        DH_free(dh);
#endif
    }
#ifndef OPENSSL_NO_EC
    else if (strcasecmp(alg, "ec") == 0)
    {
      EC_GROUP *group = openssl_get_ec_group(L, 2, 3, 4);
      if (!group)
        luaL_error(L, "failed to get ec_group object");
      EC_KEY *ec = NULL;
      ec = EC_KEY_new();
      if (ec)
      {
        EC_KEY_set_group(ec, group);
        EC_GROUP_free(group);
        if (EC_KEY_generate_key(ec))
        {
          pkey = EVP_PKEY_new();
          EVP_PKEY_assign_EC_KEY(pkey, ec);
        }
        else
          EC_KEY_free(ec);
      }
      else
        EC_GROUP_free(group);
    }
#endif
    else
    {
      luaL_error(L, "not support %s!!!!", alg);
    }
  }
  else if (lua_istable(L, 1))
  {
    lua_getfield(L, 1, "alg");
    alg = luaL_optstring(L, -1, alg);
    lua_pop(L, 1);
    if (strcasecmp(alg, "rsa") == 0)
    {
      pkey = EVP_PKEY_new();
      if (pkey)
      {
        RSA *rsa = RSA_new();
        if (rsa)
        {
          BIGNUM *n = NULL, *e = NULL, *d = NULL;
          BIGNUM *p = NULL, *q = NULL;
          BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;

          lua_getfield(L, 1, "n");
          n = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "e");
          e = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "d");
          d = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "p");
          p = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "q");
          q = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "dmp1");
          dmp1 = BN_get(L, -1);
          lua_pop(L, 1);
          lua_getfield(L, 1, "dmq1");
          dmq1 = BN_get(L, -1);
          lua_pop(L, 1);
          lua_getfield(L, 1, "iqmp");
          iqmp = BN_get(L, -1);
          lua_pop(L, 1);

          if (RSA_set0_key(rsa, n, e, d) == 1
              && (p == NULL || RSA_set0_factors(rsa, p, q) == 1)
              && (dmp1 == NULL || RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp) == 1) )
          {
            if (!EVP_PKEY_assign_RSA(pkey, rsa))
            {
              RSA_free(rsa);
              rsa = NULL;
              EVP_PKEY_free(pkey);
              pkey = NULL;
            }
          }
          else
          {
            RSA_free(rsa);
            rsa = NULL;
            EVP_PKEY_free(pkey);
            pkey = NULL;
          }
        }
      }
    }
    else if (strcasecmp(alg, "dsa") == 0)
    {
      pkey = EVP_PKEY_new();
      if (pkey)
      {
        DSA *dsa = DSA_new();
        if (dsa)
        {
          BIGNUM *p = NULL, *q = NULL, *g = NULL;
          BIGNUM *priv_key = NULL, *pub_key = NULL;

          lua_getfield(L, 1, "p");
          p = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "q");
          q = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "g");
          g = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "priv_key");
          priv_key = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "pub_key");
          pub_key = BN_get(L, -1);
          lua_pop(L, 1);

          if (DSA_set0_key(dsa, pub_key, priv_key) == 1
              && DSA_set0_pqg(dsa, p, q, g))
          {
            if (!EVP_PKEY_assign_DSA(pkey, dsa))
            {
              DSA_free(dsa);
              EVP_PKEY_free(pkey);
              pkey = NULL;
            }
          }
          else
          {
            DSA_free(dsa);
            dsa = NULL;
            EVP_PKEY_free(pkey);
            pkey = NULL;
          }
        }
      }
    }
    else if (strcasecmp(alg, "dh") == 0)
    {

      pkey = EVP_PKEY_new();
      if (pkey)
      {
        DH *dh = DH_new();
        if (dh)
        {
          BIGNUM *p = NULL, *q = NULL, *g = NULL;
          BIGNUM *priv_key = NULL, *pub_key = NULL;

          lua_getfield(L, 1, "p");
          p = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "q");
          q = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "g");
          g = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "priv_key");
          priv_key = BN_get(L, -1);
          lua_pop(L, 1);

          lua_getfield(L, 1, "pub_key");
          pub_key = BN_get(L, -1);
          lua_pop(L, 1);

          if (DH_set0_key(dh, pub_key, priv_key) == 1
              && DH_set0_pqg(dh, p, q, g))
          {
            if (!EVP_PKEY_assign_DH(pkey, dh))
            {
              DH_free(dh);
              dh = NULL;
              EVP_PKEY_free(pkey);
              pkey = NULL;
            }
          }
          else
          {
            DH_free(dh);
            dh = NULL;
            EVP_PKEY_free(pkey);
            pkey = NULL;
          }
        }
      }
    }
    else if (strcasecmp(alg, "ec") == 0)
    {
      BIGNUM *d = NULL;
      BIGNUM *x = NULL;
      BIGNUM *y = NULL;
      BIGNUM *z = NULL;
      EC_GROUP *group = NULL;

      lua_getfield(L, -1, "ec_name");
      lua_getfield(L, -2, "param_enc");
      lua_getfield(L, -3, "conv_form");
      group = openssl_get_ec_group(L, -3, -2, -1);
      lua_pop(L, 3);
      if (!group)
      {
        luaL_error(L, "get openssl.ec_group fail");
      }

      EC_GET_FIELD(d);
      EC_GET_FIELD(x);
      EC_GET_FIELD(y);
      EC_GET_FIELD(z);
      if (z)
      {
        luaL_error(L, "Only accpet affine co-ordinates");
      }

      pkey = EVP_PKEY_new();
      if (pkey)
      {
        EC_KEY *ec = EC_KEY_new();
        if (ec)
        {
          EC_KEY_set_group(ec, group);
          if (d)
            EC_KEY_set_private_key(ec, d);
          if (x != NULL && y != NULL)
          {
            EC_POINT *pnt = EC_POINT_new(group);
            EC_POINT_set_affine_coordinates(group, pnt, x, y, NULL);

            EC_KEY_set_public_key(ec, pnt);
            EC_POINT_free(pnt);
          }
          else
            EC_KEY_generate_key_part(ec);

          if (EC_KEY_check_key(ec) == 0 || EVP_PKEY_assign_EC_KEY(pkey, ec) == 0)
          {
            EC_KEY_free(ec);
            EVP_PKEY_free(pkey);
            pkey = NULL;
          }

          BN_free(d);
          BN_free(x);
          BN_free(y);
          BN_free(z);
        }
      }
      EC_GROUP_free(group);
    }
  }
  else if (auxiliar_getclassudata(L, "openssl.rsa", 1))
  {
    RSA* rsa = CHECK_OBJECT(1, RSA, "openssl.rsa");
    pkey = EVP_PKEY_new();
    EVP_PKEY_set1_RSA(pkey, rsa);
  }
  else if (auxiliar_getclassudata(L, "openssl.ec_key", 1))
  {
    EC_KEY* ec = CHECK_OBJECT(1, EC_KEY, "openssl.ec_key");
    pkey = EVP_PKEY_new();
    EVP_PKEY_set1_EC_KEY(pkey, ec);
  }
  else if (auxiliar_getclassudata(L, "openssl.dh", 1))
  {
    DH *dh= CHECK_OBJECT(1, DH, "openssl.dh");
    pkey = EVP_PKEY_new();
    EVP_PKEY_set1_DH(pkey, dh);
  }
  else if (auxiliar_getclassudata(L, "openssl.dsa", 1))
  {
    DSA *dsa = CHECK_OBJECT(1, DSA, "openssl.dsa");
    pkey = EVP_PKEY_new();
    EVP_PKEY_set1_DSA(pkey, dsa);
  }

  if (pkey && EVP_PKEY_id(pkey) != NID_undef)
  {
    PUSH_OBJECT(pkey, "openssl.evp_pkey");
    return 1;
  }
  else
    EVP_PKEY_free(pkey);
  return 0;
}

/***
openssl.evp_pkey object
@type evp_pkey
*/
/***
export evp_pkey as pem/der string
@function export
@tparam[opt='pem'] string support export as 'pem' or 'der' format, default is 'pem'
@tparam[opt=false] boolean raw true for export low layer key just rsa,dsa,ec
@tparam[opt] string passphrase if given, export key will encrypt with des-cbc-ede,
only need when export private key
@treturn string
*/
static LUA_FUNCTION(openssl_pkey_export)
{
  EVP_PKEY * key;
  int ispriv = 0;
  int exraw = 0;
  int fmt = FORMAT_AUTO;
  size_t passphrase_len = 0;
  BIO * bio_out = NULL;
  int ret = 0;
  const EVP_CIPHER * cipher;
  const char * passphrase = NULL;

  key = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  ispriv = openssl_pkey_is_private(key);

  fmt = lua_type(L, 2);
  luaL_argcheck(L, fmt == LUA_TSTRING || fmt == LUA_TNONE, 2,
                "only accept 'pem','der' or none");
  fmt = luaL_checkoption(L, 2, "pem", format);
  luaL_argcheck(L, fmt == FORMAT_PEM || fmt == FORMAT_DER, 2,
                "only accept pem or der, default is pem");

  if (!lua_isnone(L, 3))
    exraw = lua_toboolean(L, 3);
  passphrase = luaL_optlstring(L, 4, NULL, &passphrase_len);

  if (passphrase)
  {
    cipher = (EVP_CIPHER *) EVP_des_ede3_cbc();
  }
  else
  {
    cipher = NULL;
  }

  bio_out = BIO_new(BIO_s_mem());
  if (fmt == FORMAT_PEM)
  {
    if (exraw == 0)
    {
      ret = ispriv ?
            PEM_write_bio_PrivateKey(bio_out, key, cipher, (unsigned char *)passphrase, passphrase_len, NULL, NULL) :
            PEM_write_bio_PUBKEY(bio_out, key);
    }
    else
    {
      /* export raw key format */
      switch (EVP_PKEY_type(EVP_PKEY_id(key)))
      {
      case EVP_PKEY_RSA:
        ret = ispriv ? PEM_write_bio_RSAPrivateKey(bio_out, EVP_PKEY_get0_RSA(key), cipher, (unsigned char *)passphrase, passphrase_len, NULL, NULL)
              : PEM_write_bio_RSAPublicKey(bio_out, EVP_PKEY_get0_RSA(key));
        break;
      case EVP_PKEY_DSA:
      {
        ret = ispriv ? PEM_write_bio_DSAPrivateKey(bio_out, EVP_PKEY_get0_DSA(key), cipher, (unsigned char *)passphrase, passphrase_len, NULL, NULL)
              : PEM_write_bio_DSA_PUBKEY(bio_out, EVP_PKEY_get0_DSA(key));
      }
      break;
      case EVP_PKEY_DH:
        ret = PEM_write_bio_DHparams(bio_out, EVP_PKEY_get0_DH(key));
        break;
#ifndef OPENSSL_NO_EC
      case EVP_PKEY_EC:
        ret = ispriv ? PEM_write_bio_ECPrivateKey(bio_out, EVP_PKEY_get0_EC_KEY(key), cipher, (unsigned char *)passphrase, passphrase_len, NULL, NULL)
              : PEM_write_bio_EC_PUBKEY(bio_out, EVP_PKEY_get0_EC_KEY(key));
        break;
#endif
      default:
        ret = 0;
        break;
      }
    }
  }
  else
  {
    /* out put der */
    if (exraw == 0)
    {
      ret = ispriv ?
            (passphrase == NULL ? i2d_PrivateKey_bio(bio_out, key) :
             i2d_PKCS8PrivateKey_bio(bio_out, key, cipher, (char *)passphrase, passphrase_len, NULL, NULL))
            : i2d_PUBKEY_bio(bio_out, key);
    }
    else
    {
      /* output raw key, rsa, ec, dh, dsa */
      switch (EVP_PKEY_type(EVP_PKEY_id(key)))
      {
      case EVP_PKEY_RSA:
        ret = ispriv ? i2d_RSAPrivateKey_bio(bio_out, EVP_PKEY_get0_RSA(key))
              : i2d_RSAPublicKey_bio(bio_out, EVP_PKEY_get0_RSA(key));
        break;
      case EVP_PKEY_DSA:
      {
        ret = ispriv ? i2d_DSAPrivateKey_bio(bio_out, EVP_PKEY_get0_DSA(key))
              : i2d_DSA_PUBKEY_bio(bio_out, EVP_PKEY_get0_DSA(key));
      }
      break;
      case EVP_PKEY_DH:
        ret = i2d_DHparams_bio(bio_out, EVP_PKEY_get0_DH(key));
        break;
#ifndef OPENSSL_NO_EC
      case EVP_PKEY_EC:
        ret = ispriv ? i2d_ECPrivateKey_bio(bio_out, EVP_PKEY_get0_EC_KEY(key))
              : i2d_EC_PUBKEY_bio(bio_out, EVP_PKEY_get0_EC_KEY(key));
        break;
#endif
      default:
        ret = 0;
        break;
      }
    }
  }

  if (ret)
  {
    char * bio_mem_ptr;
    long bio_mem_len;

    bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr);

    lua_pushlstring(L, bio_mem_ptr, bio_mem_len);
    ret  = 1;
  }

  if (bio_out)
  {
    BIO_free(bio_out);
  }
  return ret;
}

static LUA_FUNCTION(openssl_pkey_free)
{
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  EVP_PKEY_free(pkey);
  return 0;
}

/***
get key details as table
@function parse
@treturn table infos with key bits,pkey,type, pkey may be rsa,dh,dsa, show as table with factor hex encoded bignum
*/
static LUA_FUNCTION(openssl_pkey_parse)
{
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  if (EVP_PKEY_id(pkey) != NID_undef)
  {
    lua_newtable(L);

    AUXILIAR_SET(L, -1, "bits", EVP_PKEY_bits(pkey), integer);
    AUXILIAR_SET(L, -1, "size", EVP_PKEY_size(pkey), integer);

    switch (EVP_PKEY_type(EVP_PKEY_id(pkey)))
    {
    case EVP_PKEY_RSA:
    {
      RSA* rsa = EVP_PKEY_get1_RSA(pkey);
      PUSH_OBJECT(rsa, "openssl.rsa");
      lua_setfield(L, -2, "rsa");

      AUXILIAR_SET(L, -1, "type", "rsa", string);
    }

    break;
    case EVP_PKEY_DSA:
    {
      DSA* dsa = EVP_PKEY_get1_DSA(pkey);
      PUSH_OBJECT(dsa, "openssl.dsa");
      lua_setfield(L, -2, "dsa");

      AUXILIAR_SET(L, -1, "type", "dsa", string);
    }
    break;
    case EVP_PKEY_DH:
    {
      DH* dh = EVP_PKEY_get1_DH(pkey);
      PUSH_OBJECT(dh, "openssl.dh");
      lua_rawseti(L, -2, 0);

      AUXILIAR_SET(L, -1, "type", "dh", string);
    }

    break;
#ifndef OPENSSL_NO_EC
    case EVP_PKEY_EC:
    {
      const EC_KEY* ec = EVP_PKEY_get1_EC_KEY(pkey);
      PUSH_OBJECT(ec, "openssl.ec_key");
      lua_setfield(L, -2, "ec");

      AUXILIAR_SET(L, -1, "type", "ec", string);
    }

    break;
#endif
    default:
      break;
    };
    return 1;
  }
  else
    luaL_argerror(L, 1, "not assign any keypair");
  return 0;
};

/***
encrypt message with public key
encrypt length of message must not longer than key size, if shorter will do padding,currently supports 6 padding modes.
They are: pkcs1, sslv23, no, oaep, x931, pss.
@function encrypt
@tparam string data data to be encrypted
@tparam string[opt='pkcs1'] string padding padding mode
@treturn string encrypted message
*/
static LUA_FUNCTION(openssl_pkey_encrypt)
{
  size_t dlen = 0;
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  const char *data = luaL_checklstring(L, 2, &dlen);
  int padding = openssl_get_padding(L, 3, "pkcs1");
  ENGINE *engine = lua_isnoneornil(L, 4) ? NULL : CHECK_OBJECT(4, ENGINE, "openssl.engine");
  size_t clen = EVP_PKEY_size(pkey);
  EVP_PKEY_CTX *ctx = NULL;
  int ret = 0;
  int typ = EVP_PKEY_type(EVP_PKEY_id(pkey));

  if (typ != EVP_PKEY_RSA && typ != EVP_PKEY_RSA2)
  {
    luaL_argerror(L, 2, "EVP_PKEY must be of type RSA or RSA2");
    return ret;
  }

  ctx = EVP_PKEY_CTX_new(pkey, engine);
  if (EVP_PKEY_encrypt_init(ctx) == 1)
  {
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) == 1)
    {
      byte* buf = malloc(clen);
      if (EVP_PKEY_encrypt(ctx, buf, &clen, (const unsigned char*)data, dlen) == 1)
      {
        lua_pushlstring(L, (const char*)buf, clen);
        ret = 1;
      }
      else
        ret = openssl_pushresult(L, 0);
      free(buf);
    }
    else
      ret = openssl_pushresult(L, 0);
  }
  else
    ret = openssl_pushresult(L, 0);
  EVP_PKEY_CTX_free(ctx);

  return ret;
}

/***
decrypt message with private key
pair with encrypt
@function decrypt
@tparam string data data to be decrypted
@tparam string[opt='pkcs1'] string padding padding mode
@treturn[1] string result
@treturn[2] nil
*/
static LUA_FUNCTION(openssl_pkey_decrypt)
{
  size_t dlen = 0;
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  const char *data = luaL_checklstring(L, 2, &dlen);
  int padding = openssl_get_padding(L, 3, "pkcs1");
  ENGINE *engine = lua_isnoneornil(L, 4) ? NULL : CHECK_OBJECT(4, ENGINE, "openssl.engine");
  size_t clen = EVP_PKEY_size(pkey);
  EVP_PKEY_CTX *ctx = NULL;
  int ret = 0;
  int type = EVP_PKEY_type(EVP_PKEY_id(pkey));

  if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA2)
  {
    luaL_argerror(L, 2, "EVP_PKEY must be of type RSA or RSA2");
    return ret;
  }
  ctx = EVP_PKEY_CTX_new(pkey, engine);
  if (EVP_PKEY_decrypt_init(ctx) == 1)
  {
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) == 1)
    {
      byte* buf = malloc(clen);

      if (EVP_PKEY_decrypt(ctx, buf, &clen, (const unsigned char*)data, dlen) == 1)
      {
        lua_pushlstring(L, (const char*)buf, clen);
        ret = 1;
      }
      else
        ret = openssl_pushresult(L, 0);
      free(buf);
    }
    else
      ret = openssl_pushresult(L, 0);
  }
  else
    ret = openssl_pushresult(L, 0);
  EVP_PKEY_CTX_free(ctx);
  return ret;
}

/***
return key is private or not
@function is_private
@treturn boolean ture is private or public key
*/
LUA_FUNCTION(openssl_pkey_is_private1)
{
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  int private = openssl_pkey_is_private(pkey);
  if (private == 0)
    lua_pushboolean(L, 0);
  else if (private == 1)
    lua_pushboolean(L, 1);
  else
    luaL_error(L, "openssl.evp_pkey is not support");
  return 1;
}

/***
return public key
@function get_public
@treturn evp_pkey pub
*/
static LUA_FUNCTION(openssl_pkey_get_public)
{
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  int ret = 0;
  BIO* bio = BIO_new(BIO_s_mem());
  if (i2d_PUBKEY_bio(bio, pkey))
  {
    EVP_PKEY *pub = d2i_PUBKEY_bio(bio, NULL);
    PUSH_OBJECT(pub, "openssl.evp_pkey");
    ret = 1;
  }
  BIO_free(bio);
  return ret;
}

/***
Derive public key algorithm shared secret

@function derive
@tparam evp_pkey pkey private key
@tparam evp_pkey peer public key
@tparam[opt] engine eng
@treturn string
*/
static LUA_FUNCTION(openssl_derive)
{
  int ret = 0;

  EVP_PKEY* pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  EVP_PKEY* peer = CHECK_OBJECT(2, EVP_PKEY, "openssl.evp_pkey");
  ENGINE *eng = lua_isnoneornil(L, 3) ? NULL : CHECK_OBJECT(3, ENGINE, "openssl.engine");
  EVP_PKEY_CTX *ctx;
  int ptype = EVP_PKEY_type(EVP_PKEY_id(pkey));

  if ( (ptype != EVP_PKEY_DH && EVP_PKEY_get0_DH(pkey)!=NULL)
       || (ptype != EVP_PKEY_EC && EVP_PKEY_get0_EC_KEY(pkey)!=NULL) )
  {
    luaL_argerror(L, 1, "only support DH or EC private key");
  }

  if ( ptype != EVP_PKEY_type(EVP_PKEY_id(peer)) )
    luaL_argerror(L, 2, "mismatch key type");

  ctx = EVP_PKEY_CTX_new(pkey, eng);
  if (ctx)
  {
    ret = EVP_PKEY_derive_init(ctx);
    if (ret==1)
    {
      ret = EVP_PKEY_derive_set_peer(ctx, peer);
      if (ret==1)
      {
        size_t skeylen;
        ret = EVP_PKEY_derive(ctx, NULL, &skeylen);
        if (ret==1)
        {
          unsigned char *skey = OPENSSL_malloc(skeylen);
          if (skey)
          {
            ret = EVP_PKEY_derive(ctx, skey, &skeylen);
            lua_pushlstring(L, (const char*)skey, skeylen);
            OPENSSL_free(skey);
          }
          else
          {
            lua_pushnil(L);
            lua_pushstring(L, "Out of memory");
            ret = 2;
          }
        }
      }
    }
    else if (ret==-2)
    {
      lua_pushnil(L);
      lua_pushstring(L, "not supported by the public key algorithm");
      ret = 2;
    }
    EVP_PKEY_CTX_free(ctx);
  }
  else
  {
    lua_pushnil(L);
    lua_pushstring(L, "Out of memory");
  }

  if (ret<1)
    ret = openssl_pushresult(L, ret);
  return ret;
}

/***
sign message with private key
@function sign
@tparam string data data be signed
@tparam[opt] string|env_digest md_alg default use sha256 or sm3 when pkey is SM2 type
@tparam[opt='1234567812345678'] string userId used when pkey is SM2 type
@treturn string signed message
*/
static LUA_FUNCTION(openssl_sign)
{
  int ret;
  size_t data_len;
  const char *data;
  const char *md_alg;
  EVP_PKEY *pkey;
  const EVP_MD *md;
  EVP_MD_CTX *ctx;

#if defined(OPENSSL_SUPPORT_SM2)
  EVP_PKEY_CTX* pctx;
  const char* userId = NULL;
  size_t idlen = 0;
  int is_SM2 = 0;
#endif

  pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  data = luaL_checklstring(L, 2, &data_len);

  md_alg = "sha256";
#if defined(OPENSSL_SUPPORT_SM2)
  is_SM2 = EVP_PKEY_id(pkey)==EVP_PKEY_SM2;
  if (is_SM2)
    md_alg = "sm3";
#endif

  md = get_digest(L, 3, md_alg);

#if defined(OPENSSL_SUPPORT_SM2)
  if (is_SM2)
    userId = luaL_optlstring (L, 4, SM2_DEFAULT_USERID, &idlen);
#endif

  ctx = EVP_MD_CTX_create();
#if defined(OPENSSL_SUPPORT_SM2)
  if (is_SM2)
  {
    pctx = EVP_PKEY_CTX_new(pkey, NULL);
    EVP_PKEY_CTX_set1_id(pctx, userId, idlen);
    EVP_MD_CTX_set_pkey_ctx(ctx, pctx);
  }
#endif

  ret = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey);
  if (ret == 1)
  {
    ret = EVP_DigestSignUpdate(ctx, data, data_len);
    if (ret == 1)
    {
      size_t siglen = 0;
      unsigned char *sigbuf = NULL;
      ret = EVP_DigestSignFinal(ctx, NULL, &siglen);
      if (ret == 1)
      {
        siglen += 2;
        sigbuf = OPENSSL_malloc(siglen);
        ret = EVP_DigestSignFinal(ctx, sigbuf, &siglen);
        if (ret == 1)
        {
          lua_pushlstring(L, (char *)sigbuf, siglen);
        }
        else
          ret = openssl_pushresult(L, ret);
        OPENSSL_free(sigbuf);
      }
      else
        ret = openssl_pushresult(L, ret);
    }
    else
      ret = openssl_pushresult(L, ret);
  }
  else
    ret = openssl_pushresult(L, ret);

  EVP_MD_CTX_destroy(ctx);
  return ret;
}

/***
verify signed message with public key
@function verify
@tparam string data data be signed
@tparam string signature signed result
@tparam[opt] string|env_digest md_alg default use sha256 or sm3 when pkey is SM2 type
@tparam[opt='1234567812345678'] string userId used when pkey is SM2 type
@treturn boolean true for pass verify
*/
static LUA_FUNCTION(openssl_verify)
{
  int ret;
  size_t data_len, signature_len;
  const char *data, *signature;
  const char *md_alg;
  EVP_PKEY *pkey;
  const EVP_MD *md;
  EVP_MD_CTX *ctx;

#if defined(OPENSSL_SUPPORT_SM2)
  EVP_PKEY_CTX* pctx;
  const char* userId = NULL;
  size_t idlen = 0;
  int is_SM2 = 0;
#endif

  pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  data = luaL_checklstring(L, 2, &data_len);
  signature = luaL_checklstring(L, 3, &signature_len);

  md_alg = "sha256";
#if defined(OPENSSL_SUPPORT_SM2)
  is_SM2 = EVP_PKEY_id(pkey)==EVP_PKEY_SM2;
  if (is_SM2)
    md_alg = "sm3";
#endif

  md = get_digest(L, 4, md_alg);

#if defined(OPENSSL_SUPPORT_SM2)
  if (is_SM2)
    userId = luaL_optlstring (L, 5, SM2_DEFAULT_USERID, &idlen);
#endif

  ctx = EVP_MD_CTX_create();
#if defined(OPENSSL_SUPPORT_SM2)
  if (is_SM2)
  {
    pctx = EVP_PKEY_CTX_new(pkey, NULL);
    EVP_PKEY_CTX_set1_id(pctx, userId, idlen);
    EVP_MD_CTX_set_pkey_ctx(ctx, pctx);
  }
#endif

  ret = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey);
  if (ret == 1)
  {
    ret = EVP_DigestVerifyUpdate(ctx, data, data_len);
    if (ret == 1)
    {
      ret = EVP_DigestVerifyFinal(ctx, (unsigned char *)signature, signature_len);
      if (ret == 1)
      {
        lua_pushboolean(L, ret == 1);
      }
      else
        ret = openssl_pushresult(L, ret);
    }
    else
      ret = openssl_pushresult(L, ret);
  }
  else
    ret = openssl_pushresult(L, ret);

  EVP_MD_CTX_destroy(ctx);
  return ret;
}

/***
seal and encrypt message with one public key
data be encrypt with secret key, secret key be encrypt with public key
@function seal
@tparam string data data to be encrypted
@tparam[opt='RC4'] cipher|string alg
@treturn string data encrypted
@treturn string skey secret key encrypted by public key
@treturn string iv
*/
static LUA_FUNCTION(openssl_seal)
{
  size_t data_len;
  const char *data = NULL;
  int nkeys = 0;
  const EVP_CIPHER *cipher = NULL;

  if (lua_istable(L, 1))
  {
    nkeys = lua_rawlen(L, 1);
    if (!nkeys)
    {
      luaL_argerror(L, 1, "empty array");
    }
  }
  else if (auxiliar_getclassudata(L, "openssl.evp_pkey", 1))
  {
    nkeys = 1;
  }
  else
    luaL_argerror(L, 1, "must be openssl.evp_pkey or unemtpy table");

  data = luaL_checklstring(L, 2, &data_len);

  cipher = get_cipher(L, 3, "rc4");

  if (cipher)
  {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    int ret = 0;
    EVP_PKEY **pkeys;
    unsigned char **eks;
    int *eksl;
    int i;
    int len1, len2;
    unsigned char *buf;
    char iv[EVP_MAX_MD_SIZE] = {0};

    pkeys = malloc(nkeys * sizeof(EVP_PKEY *));
    eksl = malloc(nkeys * sizeof(int));
    eks = malloc(nkeys * sizeof(char*));

    memset(eks, 0, sizeof(char*) * nkeys);

    /* get the public keys we are using to seal this data */
    if (lua_istable(L, 1))
    {
      for (i = 0; i < nkeys; i++)
      {
        lua_rawgeti(L, 1, i + 1);

        pkeys[i] =  CHECK_OBJECT(-1, EVP_PKEY, "openssl.evp_pkey");
        if (pkeys[i] == NULL)
        {
          luaL_argerror(L, 1, "table with gap");
        }
        eksl[i] = EVP_PKEY_size(pkeys[i]);
        eks[i] = malloc(eksl[i]);

        lua_pop(L, 1);
      }
    }
    else
    {
      pkeys[0] = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
      eksl[0] = EVP_PKEY_size(pkeys[0]);
      eks[0] = malloc(eksl[0]);
    }
    EVP_CIPHER_CTX_reset(ctx);

    /* allocate one byte extra to make room for \0 */
    len1 = data_len + EVP_CIPHER_block_size(cipher) + 1;
    buf = malloc(len1);


    if (!EVP_SealInit(ctx, cipher, eks, eksl, (unsigned char*) iv, pkeys, nkeys)
        || !EVP_SealUpdate(ctx, buf, &len1, (unsigned char *)data, data_len))
    {
      luaL_error(L, "EVP_SealInit failed");
    }

    EVP_SealFinal(ctx, buf + len1, &len2);

    if (len1 + len2 > 0)
    {
      lua_pushlstring(L, (const char*)buf, len1 + len2);
      if (lua_istable(L, 1))
      {
        lua_newtable(L);
        for (i = 0; i < nkeys; i++)
        {
          lua_pushlstring(L, (const char*)eks[i], eksl[i]);
          free(eks[i]);
          lua_rawseti(L, -2, i + 1);
        }
      }
      else
      {
        lua_pushlstring(L, (const char*)eks[0], eksl[0]);
        free(eks[0]);
      }
      lua_pushlstring(L, iv, EVP_CIPHER_CTX_iv_length(ctx));

      ret = 3;
    }

    free(buf);
    free(eks);
    free(eksl);
    free(pkeys);
    EVP_CIPHER_CTX_free(ctx);
    return ret;
  }
  else
    luaL_argerror(L, 3, "Not support cipher alg");
  return 0;
}

/***
open and ecrypted seal data with private key
@function open
@tparam string ekey encrypted secret key
@tparam string string iv
@tparam[opt='RC4'] evp_cipher|string md_alg
@treturn string data decrypted message or nil on failure
*/
static LUA_FUNCTION(openssl_open)
{
  EVP_PKEY *pkey =  CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  size_t data_len, ekey_len, iv_len;
  const char *data = luaL_checklstring(L, 2, &data_len);
  const char *ekey = luaL_checklstring(L, 3, &ekey_len);
  const char *iv = luaL_checklstring(L, 4, &iv_len);

  int ret = 0;
  int len1, len2 = 0;
  unsigned char *buf;

  const EVP_CIPHER *cipher = NULL;

  cipher = get_cipher(L, 5, "rc4");

  if (cipher)
  {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    len1 = data_len + 1;
    buf = malloc(len1);

    EVP_CIPHER_CTX_reset(ctx);
    if (EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, ekey_len, (const unsigned char *)iv, pkey)
        && EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, data_len))
    {
      len2 = data_len - len1;
      if (!EVP_OpenFinal(ctx, buf + len1, &len2) || (len1 + len2 == 0))
      {
        luaL_error(L, "EVP_OpenFinal() failed.");
        ret = 0;
      }
    }
    else
    {
      luaL_error(L, "EVP_OpenInit() failed.");
      ret = 0;
    }
    EVP_CIPHER_CTX_free(ctx);
    lua_pushlstring(L, (const char*)buf, len1 + len2);
    free(buf);
    ret = 1;
  }
  else
    luaL_argerror(L, 5, "Not support cipher alg");

  return ret;
}

static LUA_FUNCTION(openssl_seal_init)
{
  int nkeys = 0;
  const EVP_CIPHER *cipher = NULL;

  if (lua_istable(L, 1))
  {
    nkeys = lua_rawlen(L, 1);
    if (!nkeys)
    {
      luaL_argerror(L, 1, "empty array");
    }
  }
  else if (auxiliar_getclassudata(L, "openssl.evp_pkey", 1))
  {
    nkeys = 1;
  }
  else
    luaL_argerror(L, 1, "must be openssl.evp_pkey or unemtpy table");

  cipher = get_cipher(L, 2, "rc4");

  if (cipher)
  {
    EVP_PKEY **pkeys;
    unsigned char **eks;
    int *eksl;
    EVP_CIPHER_CTX *ctx = NULL;

    int i;
    char iv[EVP_MAX_MD_SIZE] = {0};

    pkeys = malloc(nkeys * sizeof(*pkeys));
    eksl = malloc(nkeys * sizeof(*eksl));
    eks = malloc(nkeys * sizeof(*eks));

    memset(eks, 0, sizeof(*eks) * nkeys);

    /* get the public keys we are using to seal this data */
    if (lua_istable(L, 1))
    {
      for (i = 0; i < nkeys; i++)
      {
        lua_rawgeti(L, 1, i + 1);

        pkeys[i] =  CHECK_OBJECT(-1, EVP_PKEY, "openssl.evp_pkey");
        if (pkeys[i] == NULL)
        {
          luaL_argerror(L, 1, "table with gap");
        }
        eksl[i] = EVP_PKEY_size(pkeys[i]);
        eks[i] = malloc(eksl[i]);

        lua_pop(L, 1);
      }
    }
    else
    {
      pkeys[0] = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
      eksl[0] = EVP_PKEY_size(pkeys[0]);
      eks[0] = malloc(eksl[0]);
    }
    ctx = EVP_CIPHER_CTX_new();
    if (!EVP_EncryptInit(ctx, cipher, NULL, NULL))
    {
      luaL_error(L, "EVP_EncryptInit failed");
    }
    if (!EVP_SealInit(ctx, cipher, eks, eksl, (unsigned char*) iv, pkeys, nkeys))
    {
      luaL_error(L, "EVP_SealInit failed");
    }
    PUSH_OBJECT(ctx, "openssl.evp_cipher_ctx");
    if (lua_istable(L, 1))
    {
      lua_newtable(L);
      for (i = 0; i < nkeys; i++)
      {
        lua_pushlstring(L, (const char*)eks[i], eksl[i]);
        free(eks[i]);
        lua_rawseti(L, -2, i + 1);
      }
    }
    else
    {
      lua_pushlstring(L, (const char*)eks[0], eksl[0]);
      free(eks[0]);
    }
    lua_pushlstring(L, iv, EVP_CIPHER_CTX_iv_length(ctx));

    free(eks);
    free(eksl);
    free(pkeys);
    return 3;
  }
  else
  {
    luaL_argerror(L, 2, "Not support cipher alg");
  }
  return 0;
}

static LUA_FUNCTION(openssl_seal_update)
{
  EVP_CIPHER_CTX* ctx = CHECK_OBJECT(1, EVP_CIPHER_CTX, "openssl.evp_cipher_ctx");
  size_t data_len;
  const char *data = luaL_checklstring(L, 2, &data_len);
  int len = data_len + EVP_CIPHER_CTX_block_size(ctx);
  unsigned char *buf =  malloc(len);

  if (!EVP_SealUpdate(ctx, buf, &len, (unsigned char *)data, data_len))
  {
    free(buf);
    luaL_error(L, "EVP_SealUpdate fail");
  }

  lua_pushlstring(L, (const char*)buf, len);
  free(buf);
  return 1;
}

static LUA_FUNCTION(openssl_seal_final)
{
  EVP_CIPHER_CTX* ctx = CHECK_OBJECT(1, EVP_CIPHER_CTX, "openssl.evp_cipher_ctx");
  int len = EVP_CIPHER_CTX_block_size(ctx);
  unsigned char *buf = malloc(len);


  if (!EVP_SealFinal(ctx, buf, &len))
  {
    free(buf);
    luaL_error(L, "EVP_SealFinal fail");
  }

  lua_pushlstring(L, (const char*)buf, len);
  return 1;
}

static LUA_FUNCTION(openssl_open_init)
{
  EVP_PKEY *pkey =  CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  size_t ekey_len, iv_len;
  const char *ekey = luaL_checklstring(L, 2, &ekey_len);
  const char *iv = luaL_checklstring(L, 3, &iv_len);

  const EVP_CIPHER *cipher = NULL;

  cipher = get_cipher(L, 4, "rc4");

  if (cipher)
  {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

    EVP_CIPHER_CTX_reset(ctx);
    if (EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, ekey_len, (const unsigned char *)iv, pkey))
    {
      PUSH_OBJECT(ctx, "openssl.evp_cipher_ctx");
      return 1;
    }
    else
    {
      luaL_error(L, "EVP_OpenInit fail");
    }
  }
  else
    luaL_argerror(L, 5, "Not support cipher alg");
  return 0;
};

static LUA_FUNCTION(openssl_open_update)
{
  EVP_CIPHER_CTX* ctx = CHECK_OBJECT(1, EVP_CIPHER_CTX, "openssl.evp_cipher_ctx");
  size_t data_len;
  const char* data = luaL_checklstring(L, 2, &data_len);

  int len = EVP_CIPHER_CTX_block_size(ctx) + data_len;
  unsigned char *buf = malloc(len);

  if (EVP_OpenUpdate(ctx, buf, &len, (unsigned char *)data, data_len))
  {
    lua_pushlstring(L, (const char*)buf, len);
  }
  else
    luaL_error(L, "EVP_OpenUpdate fail");

  free(buf);
  return 1;
}

static LUA_FUNCTION(openssl_open_final)
{
  EVP_CIPHER_CTX* ctx = CHECK_OBJECT(1, EVP_CIPHER_CTX, "openssl.evp_cipher_ctx");
  int len = EVP_CIPHER_CTX_block_size(ctx);
  unsigned char *buf = malloc(len);
  int ret = EVP_OpenFinal(ctx, buf, &len);
  if (ret == 1)
  {
    lua_pushlstring(L, (const char*)buf, len);
  }
  free(buf);
  return ret == 1 ? ret : openssl_pushresult(L, ret);
}

static int openssl_pkey_bits(lua_State *L)
{
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  lua_Integer ret = EVP_PKEY_bits(pkey);
  lua_pushinteger(L, ret);
  return  1;
};

static int openssl_pkey_set_engine(lua_State *L)
{
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  ENGINE *eng = CHECK_OBJECT(2, ENGINE, "openssl.engine");

  int ret = -1;
  int typ = EVP_PKEY_type(EVP_PKEY_id(pkey));
  switch (typ)
  {
#ifndef OPENSSL_NO_RSA
  case EVP_PKEY_RSA:
  {
    RSA *rsa = EVP_PKEY_get0_RSA(pkey);
    const RSA_METHOD *m = ENGINE_get_RSA(eng);
    if (m!=NULL)
      ret = RSA_set_method(rsa, m);
    break;
  }
#endif
#ifndef OPENSSL_NO_DSA
  case EVP_PKEY_DSA:
  {
    DSA *dsa = EVP_PKEY_get0_DSA(pkey);
    const DSA_METHOD *m = ENGINE_get_DSA(eng);
    if (m!=NULL)
      ret = DSA_set_method(dsa, m);
    break;
  }
#endif
#ifndef OPENSSL_NO_DH
  case EVP_PKEY_DH:
  {
    DH *dh = EVP_PKEY_get0_DH(pkey);
    const DH_METHOD *m = ENGINE_get_DH(eng);
    if (m!=NULL)
      ret = DH_set_method(dh, m);
    break;
  }
#endif
#ifndef OPENSSL_NO_EC
  case EVP_PKEY_EC:
  {
    EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey);
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
    const ECDSA_METHOD *m = ENGINE_get_ECDSA(eng);
    if (m!=NULL)
      ret = ECDSA_set_method(ec, m);
#else
    const EC_KEY_METHOD *m = ENGINE_get_EC(eng);
    if (m!=NULL)
      ret = EC_KEY_set_method(ec, m);
#endif
    break;
  }
#endif
  default:
    break;
  }
  if (ret==-1)
    return 0;
  lua_pushboolean(L, ret);
  return 1;
}

#if defined(OPENSSL_SUPPORT_SM2)
static int openssl_pkey_as_sm2(lua_State *L)
{
  EVP_PKEY *pkey = CHECK_OBJECT(1, EVP_PKEY, "openssl.evp_pkey");
  int type = EVP_PKEY_type(EVP_PKEY_id(pkey));
  if(type==EVP_PKEY_EC)
  {
    const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey);
    const EC_GROUP *grp = EC_KEY_get0_group(ec);
    int curve = EC_GROUP_get_curve_name(grp);
    if (curve==NID_sm2)
    {
      EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2);
      lua_pushboolean(L, 1);
      return 1;
    }
  }
  return luaL_argerror (L, 1, "must be EC key with SM2 curve");
}
#endif

static luaL_Reg pkey_funcs[] =
{
  {"is_private",    openssl_pkey_is_private1},
  {"get_public",    openssl_pkey_get_public},
  {"set_engine",    openssl_pkey_set_engine},

  {"export",        openssl_pkey_export},
  {"parse",         openssl_pkey_parse},
  {"bits",          openssl_pkey_bits},

  {"encrypt",       openssl_pkey_encrypt},
  {"decrypt",       openssl_pkey_decrypt},
  {"sign",          openssl_sign},
  {"verify",        openssl_verify},

  {"seal",          openssl_seal},
  {"open",          openssl_open},

  {"derive",        openssl_derive},

#if defined(OPENSSL_SUPPORT_SM2)
  {"as_sm2",        openssl_pkey_as_sm2},
#endif

  {"__gc",          openssl_pkey_free},
  {"__tostring",    auxiliar_tostring},

  {NULL,            NULL},
};

static const luaL_Reg R[] =
{
  {"read",          openssl_pkey_read},
  {"new",           openssl_pkey_new},

  {"seal",          openssl_seal},
  {"seal_init",     openssl_seal_init},
  {"seal_update",   openssl_seal_update},
  {"seal_final",    openssl_seal_final},
  {"open",          openssl_open},
  {"open_init",     openssl_open_init},
  {"open_update",   openssl_open_update},
  {"open_final",    openssl_open_final},

  {"get_public",    openssl_pkey_get_public},
  {"set_engine",    openssl_pkey_set_engine},
  {"is_private",    openssl_pkey_is_private1},
  {"export",        openssl_pkey_export},
  {"parse",         openssl_pkey_parse},
  {"bits",          openssl_pkey_bits},

  {"encrypt",       openssl_pkey_encrypt},
  {"decrypt",       openssl_pkey_decrypt},
  {"sign",          openssl_sign},
  {"verify",        openssl_verify},
  {"derive",        openssl_derive},

#if defined(OPENSSL_SUPPORT_SM2)
  {"as_sm2",        openssl_pkey_as_sm2},
#endif

  {NULL,  NULL}
};

int luaopen_pkey(lua_State *L)
{
  auxiliar_newclass(L, "openssl.evp_pkey", pkey_funcs);

  lua_newtable(L);
  luaL_setfuncs(L, R, 0);

  return 1;
}
