/***
PKCS7 module for lua-openssl binding

@module pkcs7
@usage
  pkcs7 = require('openssl').pkcs7
*/
#include "openssl.h"
#include "private.h"

#define MYNAME    "pkcs7"
#define MYVERSION MYNAME " library for " LUA_VERSION " / Nov 2014 / "\
  "based on OpenSSL " SHLIB_VERSION_NUMBER

/***
read string or bio object, which include pkcs7 content

@function read
@tparam bio|string input
@tparam[opt='auto'] format allow 'auto','der','pem','smime'
 auto will only try 'der' or 'pem'
@treturn pkcs7 object or nil
@treturn string content exist only smime format
*/
static LUA_FUNCTION(openssl_pkcs7_read)
{
  BIO* bio = load_bio_object(L, 1);
  int fmt = luaL_checkoption(L, 2, "auto", format);
  PKCS7 *p7 = NULL;
  BIO* ctx = NULL;

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

  if (fmt == FORMAT_DER)
  {
    p7 = d2i_PKCS7_bio(bio, NULL);
    (void)BIO_reset(bio);
  }
  else if (fmt == FORMAT_PEM)
  {
    p7 = PEM_read_bio_PKCS7(bio, NULL, NULL, NULL);
    (void)BIO_reset(bio);
  }
  else if (fmt == FORMAT_SMIME)
  {
    p7 = SMIME_read_PKCS7(bio, &ctx);
  }

  BIO_free(bio);
  if (p7)
  {
    PUSH_OBJECT(p7, "openssl.pkcs7");
    if (ctx)
    {
      BUF_MEM* mem;
      BIO_get_mem_ptr(ctx, &mem);
      lua_pushlstring(L, mem->data, mem->length);
      BIO_free(ctx);
      return 2;
    }
    return 1;
  }
  return openssl_pushresult(L, 0);
}

#if OPENSSL_VERSION_NUMBER > 0x10000000L
/***
create new empty pkcs7 object, which support flexble sign methods.

@function new
@tparam[opt=NID_pkcs7_signed] int oid given pkcs7 type
@tparam[opt=NID_pkcs7_data] int content given pkcs7 content type
@treturn pkcs7 object
*/
static LUA_FUNCTION(openssl_pkcs7_new)
{
  int type = luaL_optint(L, 1, NID_pkcs7_signed);
  int content_nid = luaL_optint(L, 2, NID_pkcs7_data);

  PKCS7 *p7 = PKCS7_new();
  if (p7)
  {
    int ret = 1;
    ret = PKCS7_set_type(p7, type);
    if (ret)
      ret = PKCS7_content_new(p7, content_nid);
    if (ret)
    {
      PUSH_OBJECT(p7, "openssl.pkcs7");
      return 1;
    }
    else
      PKCS7_free(p7);
  }
  return 0;
}

static LUA_FUNCTION(openssl_pkcs7_add)
{
  PKCS7 *p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  int n = lua_gettop(L);
  int i, ret;
  ret = 1;
  luaL_argcheck(L, lua_isuserdata(L, 2), 2, "must supply certificate or crl object");
  for (i = 2; i <= n; i++)
  {
    luaL_argcheck(L, auxiliar_getclassudata(L, "openssl.x509", i) || auxiliar_getclassudata(L, "openssl.x509_crl", i),
                  i, "must supply certificate or crl object");

    if (auxiliar_getclassudata(L, "openssl.x509", i))
    {
      X509* x = CHECK_OBJECT(i, X509, "openssl.x509");
      ret = PKCS7_add_certificate(p7, x);
    }
    else
    {
      X509_CRL *crl = CHECK_OBJECT(i, X509_CRL, "openssl.x509_crl");
      ret = PKCS7_add_crl(p7, crl);
    }
    luaL_argcheck(L, ret, i, "add to pkcs7 fail");
  }
  return openssl_pushresult(L, ret);
}

static int PKCS7_type_is_other(PKCS7* p7)
{
  int isOther = 1;

  int nid = OBJ_obj2nid(p7->type);

  switch (nid)
  {
  case NID_pkcs7_data:
  case NID_pkcs7_signed:
  case NID_pkcs7_enveloped:
  case NID_pkcs7_signedAndEnveloped:
  case NID_pkcs7_digest:
  case NID_pkcs7_encrypted:
    isOther = 0;
    break;
  default:
    isOther = 1;
  }

  return isOther;
}

static ASN1_OCTET_STRING *PKCS7_get_octet_string(PKCS7 *p7)
{
  if (PKCS7_type_is_data(p7))
    return p7->d.data;
  if (PKCS7_type_is_other(p7) && p7->d.other
      && (p7->d.other->type == V_ASN1_OCTET_STRING))
    return p7->d.other->value.octet_string;
  return NULL;
}

static BIO *PKCS7_find_digest(EVP_MD_CTX **pmd, BIO *bio, int nid)
{
  for (;;)
  {
    bio = BIO_find_type(bio, BIO_TYPE_MD);
    if (bio == NULL)
    {
      PKCS7err(PKCS7_F_PKCS7_FIND_DIGEST,
               PKCS7_R_UNABLE_TO_FIND_MESSAGE_DIGEST);
      return NULL;
    }
    BIO_get_md_ctx(bio, pmd);
    if (*pmd == NULL)
    {
      PKCS7err(PKCS7_F_PKCS7_FIND_DIGEST, ERR_R_INTERNAL_ERROR);
      return NULL;
    }
    if (EVP_MD_CTX_type(*pmd) == nid)
      return bio;
    bio = BIO_next(bio);
  }
}

static int PKCS7_SIGNER_INFO_sign_0(PKCS7_SIGNER_INFO *si)
{
  EVP_MD_CTX *mctx;
  EVP_PKEY_CTX *pctx;
  unsigned char *abuf = NULL;
  int alen;
  size_t siglen;
  const EVP_MD *md = NULL;

  md = EVP_get_digestbyobj(si->digest_alg->algorithm);
  if (md == NULL)
    return 0;

  mctx = EVP_MD_CTX_new();
  EVP_MD_CTX_init(mctx);
  if (EVP_DigestSignInit(mctx, &pctx, md, NULL, si->pkey) <= 0)
    goto err;

  if (EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_SIGN,
                        EVP_PKEY_CTRL_PKCS7_SIGN, 0, si) <= 0)
  {
    PKCS7err(PKCS7_F_PKCS7_SIGNER_INFO_SIGN, PKCS7_R_CTRL_ERROR);
    goto err;
  }

  alen = ASN1_item_i2d((ASN1_VALUE *) si->auth_attr, &abuf,
                       ASN1_ITEM_rptr(PKCS7_ATTR_SIGN));
  if (!abuf)
    goto err;
  if (EVP_DigestSignUpdate(mctx, abuf, alen) <= 0)
    goto err;
  OPENSSL_free(abuf);
  abuf = NULL;
  if (EVP_DigestSignFinal(mctx, NULL, &siglen) <= 0)
    goto err;
  abuf = OPENSSL_malloc(siglen);
  if (!abuf)
    goto err;

  if (EVP_DigestSignFinal(mctx, abuf, &siglen) <= 0)
    goto err;
  if (EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_SIGN,
                        EVP_PKEY_CTRL_PKCS7_SIGN, 1, si) <= 0)
  {
    PKCS7err(PKCS7_F_PKCS7_SIGNER_INFO_SIGN, PKCS7_R_CTRL_ERROR);
    goto err;
  }

  EVP_MD_CTX_free(mctx);

  ASN1_STRING_set0(si->enc_digest, abuf, siglen);

  return 1;

err:
  if (abuf)
    OPENSSL_free(abuf);
  EVP_MD_CTX_free(mctx);
  return 0;

}

static int do_pkcs7_signed_attrib(PKCS7_SIGNER_INFO *si, EVP_MD_CTX *mctx)
{
  unsigned char md_data[EVP_MAX_MD_SIZE];
  unsigned int md_len;

  /* Add signing time if not already present */
  if (!PKCS7_get_signed_attribute(si, NID_pkcs9_signingTime))
  {
    if (!PKCS7_add0_attrib_signing_time(si, NULL))
    {
      PKCS7err(PKCS7_F_DO_PKCS7_SIGNED_ATTRIB, ERR_R_MALLOC_FAILURE);
      return 0;
    }
  }

  /* Add digest */
  if (!EVP_DigestFinal_ex(mctx, md_data, &md_len))
  {
    PKCS7err(PKCS7_F_DO_PKCS7_SIGNED_ATTRIB, ERR_R_EVP_LIB);
    return 0;
  }
  if (!PKCS7_add1_attrib_digest(si, md_data, md_len))
  {
    PKCS7err(PKCS7_F_DO_PKCS7_SIGNED_ATTRIB, ERR_R_MALLOC_FAILURE);
    return 0;
  }

  /* Now sign the attributes */
  if (!PKCS7_SIGNER_INFO_sign_0(si))
    return 0;

  return 1;
}

static char *memdup(const char *src, size_t buffer_length)
{
  size_t length;
  int add = 0;
  char *buffer;

  if (buffer_length)
    length = buffer_length;
  else if (src)
  {
    length = strlen(src);
    add = 1;
  }
  else
    /* no length and a NULL src pointer! */
    return strdup("");

  buffer = malloc(length + add);
  if (!buffer)
    return NULL; /* fail */

  memcpy(buffer, src, length);

  /* if length unknown do null termination */
  if (add)
    buffer[length] = '\0';

  return buffer;
}


static int openssl_pkcs7_dataFinal(PKCS7 *p7, BIO *bio)
{
  int ret = 0;
  int i, j;
  BIO *btmp;
  PKCS7_SIGNER_INFO *si;
  EVP_MD_CTX *mdc, *ctx_tmp;
  STACK_OF(X509_ATTRIBUTE) *sk;
  STACK_OF(PKCS7_SIGNER_INFO) *si_sk = NULL;
  ASN1_OCTET_STRING *os = NULL;

  if (p7 == NULL)
  {
    PKCS7err(PKCS7_F_PKCS7_DATAFINAL, PKCS7_R_INVALID_NULL_POINTER);
    return 0;
  }

  if (p7->d.ptr == NULL)
  {
    PKCS7err(PKCS7_F_PKCS7_DATAFINAL, PKCS7_R_NO_CONTENT);
    return 0;
  }
  ctx_tmp = EVP_MD_CTX_new();
  EVP_MD_CTX_init(ctx_tmp);
  i = OBJ_obj2nid(p7->type);
  p7->state = PKCS7_S_HEADER;

  switch (i)
  {
  case NID_pkcs7_data:
    os = p7->d.data;
    break;
  case NID_pkcs7_signedAndEnveloped:
    /* XXXXXXXXXXXXXXXX */
    si_sk = p7->d.signed_and_enveloped->signer_info;
    os = p7->d.signed_and_enveloped->enc_data->enc_data;
    if (!os)
    {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
      os = M_ASN1_OCTET_STRING_new();
#else
      os = ASN1_OCTET_STRING_new();
#endif
      if (!os)
      {
        PKCS7err(PKCS7_F_PKCS7_DATAFINAL, ERR_R_MALLOC_FAILURE);
        goto err;
      }
      p7->d.signed_and_enveloped->enc_data->enc_data = os;
    }
    break;
  case NID_pkcs7_enveloped:
    /* XXXXXXXXXXXXXXXX */
    os = p7->d.enveloped->enc_data->enc_data;
    if (!os)
    {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
      os = M_ASN1_OCTET_STRING_new();
#else
      os = ASN1_OCTET_STRING_new();
#endif
      if (!os)
      {
        PKCS7err(PKCS7_F_PKCS7_DATAFINAL, ERR_R_MALLOC_FAILURE);
        goto err;
      }
      p7->d.enveloped->enc_data->enc_data = os;
    }
    break;
  case NID_pkcs7_signed:
    si_sk = p7->d.sign->signer_info;
    os = PKCS7_get_octet_string(p7->d.sign->contents);
    /* If detached data then the content is excluded */
    if (PKCS7_type_is_data(p7->d.sign->contents) && p7->detached)
    {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
      M_ASN1_OCTET_STRING_free(os);
#else
      ASN1_OCTET_STRING_free(os);
#endif
      os = NULL;
      p7->d.sign->contents->d.data = NULL;
    }
    break;

  case NID_pkcs7_digest:
    os = PKCS7_get_octet_string(p7->d.digest->contents);
    /* If detached data then the content is excluded */
    if (PKCS7_type_is_data(p7->d.digest->contents) && p7->detached)
    {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
      M_ASN1_OCTET_STRING_free(os);
#else
      ASN1_OCTET_STRING_free(os);
#endif
      os = NULL;
      p7->d.digest->contents->d.data = NULL;
    }
    break;

  default:
    PKCS7err(PKCS7_F_PKCS7_DATAFINAL, PKCS7_R_UNSUPPORTED_CONTENT_TYPE);
    goto err;
  }

  if (si_sk != NULL)
  {
    for (i = 0; i < sk_PKCS7_SIGNER_INFO_num(si_sk); i++)
    {
      si = sk_PKCS7_SIGNER_INFO_value(si_sk, i);
      if (si->pkey == NULL)
        continue;

      j = OBJ_obj2nid(si->digest_alg->algorithm);

      btmp = bio;

      btmp = PKCS7_find_digest(&mdc, btmp, j);

      if (btmp == NULL)
        goto err;

      /*
      * We now have the EVP_MD_CTX, lets do the signing.
      */
      if (!EVP_MD_CTX_copy_ex(ctx_tmp, mdc))
        goto err;

      sk = si->auth_attr;

      /*
      * If there are attributes, we add the digest attribute and only
      * sign the attributes
      */
      if (sk_X509_ATTRIBUTE_num(sk) > 0)
      {
        if (!do_pkcs7_signed_attrib(si, ctx_tmp))
          goto err;
      }
      else
      {
        unsigned char *abuf = NULL;
        unsigned int abuflen;
        abuflen = EVP_PKEY_size(si->pkey);
        abuf = OPENSSL_malloc(abuflen);
        if (!abuf)
          goto err;

        if (!EVP_SignFinal(ctx_tmp, abuf, &abuflen, si->pkey))
        {
          PKCS7err(PKCS7_F_PKCS7_DATAFINAL, ERR_R_EVP_LIB);
          goto err;
        }
        ASN1_STRING_set0(si->enc_digest, abuf, abuflen);
      }
    }
  }
  else if (i == NID_pkcs7_digest)
  {
    unsigned char md_data[EVP_MAX_MD_SIZE];
    unsigned int md_len;
    if (!PKCS7_find_digest(&mdc, bio,
                           OBJ_obj2nid(p7->d.digest->md->algorithm)))
      goto err;
    if (!EVP_DigestFinal_ex(mdc, md_data, &md_len))
      goto err;
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
    M_ASN1_OCTET_STRING_set(p7->d.digest->digest, md_data, md_len);
#else
    ASN1_OCTET_STRING_set(p7->d.digest->digest, md_data, md_len);
#endif
  }

  if (!PKCS7_is_detached(p7))
  {
    /*
    * NOTE(emilia): I think we only reach os == NULL here because detached
    * digested data support is broken.
    */
    if (os == NULL)
      goto err;
    if (!(os->flags & ASN1_STRING_FLAG_NDEF))
    {
      char *cont;
      long contlen;
      btmp = BIO_find_type(bio, BIO_TYPE_MEM);
      if (btmp == NULL)
      {
        PKCS7err(PKCS7_F_PKCS7_DATAFINAL, PKCS7_R_UNABLE_TO_FIND_MEM_BIO);
        goto err;
      }
      contlen = BIO_get_mem_data(btmp, &cont);
      /*
      * Mark the BIO read only then we can use its copy of the data
      * instead of making an extra copy.
      */
      BIO_set_flags(btmp, BIO_FLAGS_MEM_RDONLY);
      BIO_set_mem_eof_return(btmp, 0);
      ASN1_STRING_set0(os, (unsigned char *)cont, contlen);
    }
  }
  ret = 1;
err:
  EVP_MD_CTX_free(ctx_tmp);
  return (ret);
}


static int openssl_pkcs7_final(lua_State *L)
{
  PKCS7 *p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  BIO *data = load_bio_object(L, 2);
  int flags = luaL_optint(L, 3, 0);

  BIO *p7bio = PKCS7_dataInit(p7, NULL);
  int ret = 0;

  if (p7bio == NULL)
  {
    lua_pushnil(L);
    lua_pushstring(L, "PKCS7_dataInit fail");
    ret = 2;
  }
  else
  {
    SMIME_crlf_copy(data, p7bio, flags);

    (void)BIO_flush(p7bio);

    if (!openssl_pkcs7_dataFinal(p7, p7bio))
    {
      lua_pushnil(L);
      lua_pushstring(L, "PKCS7_dataFinal fail");
      ret = 2;
    }
    else
    {
      ret = 1;
      lua_pushboolean(L, 1);
    }
    BIO_free_all(p7bio);
  }

  return ret;
}

int PKCS7_signatureVerify_digest(PKCS7 *p7, PKCS7_SIGNER_INFO *si, X509 *x509,
                                 const unsigned char* data, size_t len, int hash)
{
  ASN1_OCTET_STRING *os;
  const EVP_MD* md;
  EVP_MD_CTX *mdc, *mdc_tmp;
  int ret = 0, i;
  int md_type;
  STACK_OF(X509_ATTRIBUTE) *sk;
  EVP_PKEY *pkey = NULL;

  mdc = EVP_MD_CTX_new();
  mdc_tmp = EVP_MD_CTX_new();
  EVP_MD_CTX_init(mdc);
  EVP_MD_CTX_init(mdc_tmp);
  if (!PKCS7_type_is_signed(p7) && !PKCS7_type_is_signedAndEnveloped(p7))
  {
    PKCS7err(PKCS7_F_PKCS7_SIGNATUREVERIFY, PKCS7_R_WRONG_PKCS7_TYPE);
    goto err;
  }

  md_type = OBJ_obj2nid(si->digest_alg->algorithm);
  md = EVP_get_digestbynid(md_type);
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
  if (!md || !data || (hash && len != (size_t) md->ctx_size) )
    goto err;

  if (!EVP_DigestInit_ex(mdc, md, NULL))
    goto err;
  if (hash)
    memcpy(mdc->md_data, data, len);
  else
    EVP_DigestUpdate(mdc, data, len);
#else
  if (!md || !data || (hash && len != (size_t)EVP_MD_meth_get_app_datasize(md)) )
    goto err;

  if (!EVP_DigestInit_ex(mdc, md, NULL))
    goto err;
  if (hash)
    memcpy(EVP_MD_CTX_md_data(mdc), data, len);
  else
    EVP_DigestUpdate(mdc, data, len);
#endif

  pkey = X509_get_pubkey(x509);
  if (!pkey)
  {
    ret = -1;
    goto err;
  }
  /*
  * mdc is the digest ctx that we want, unless there are attributes, in
  * which case the digest is the signed attributes
  */
  if (!EVP_MD_CTX_copy_ex(mdc_tmp, mdc))
    goto err;
  sk = si->auth_attr;
  if ((sk != NULL) && (sk_X509_ATTRIBUTE_num(sk) != 0))
  {
    unsigned char md_dat[EVP_MAX_MD_SIZE], *abuf = NULL;
    unsigned int md_len;
    int alen;
    ASN1_OCTET_STRING *message_digest;

    if (!EVP_DigestFinal_ex(mdc_tmp, md_dat, &md_len))
      goto err;
    message_digest = PKCS7_digest_from_attributes(sk);
    if (!message_digest)
    {
      PKCS7err(PKCS7_F_PKCS7_SIGNATUREVERIFY,
               PKCS7_R_UNABLE_TO_FIND_MESSAGE_DIGEST);
      goto err;
    }
    if ((message_digest->length != (int) md_len) ||
        (memcmp(message_digest->data, md_dat, md_len)))
    {
      PKCS7err(PKCS7_F_PKCS7_SIGNATUREVERIFY, PKCS7_R_DIGEST_FAILURE);
      ret = -1;
      goto err;
    }
    if (!EVP_DigestVerifyInit(mdc_tmp, NULL, EVP_get_digestbynid(md_type), NULL, pkey))
      goto err;

    alen = ASN1_item_i2d((ASN1_VALUE *) sk, &abuf,
                         ASN1_ITEM_rptr(PKCS7_ATTR_VERIFY));
    if (alen <= 0)
    {
      PKCS7err(PKCS7_F_PKCS7_SIGNATUREVERIFY, ERR_R_ASN1_LIB);
      ret = -1;
      goto err;
    }
    if (!EVP_VerifyUpdate(mdc_tmp, abuf, alen))
      goto err;

    OPENSSL_free(abuf);
  }

  os = si->enc_digest;
  i = EVP_VerifyFinal(mdc_tmp, os->data, os->length, pkey);
  if (i <= 0)
  {
    PKCS7err(PKCS7_F_PKCS7_SIGNATUREVERIFY, PKCS7_R_SIGNATURE_FAILURE);
    ret = -1;
    goto err;
  }
  else
    ret = 1;
err:
  EVP_PKEY_free(pkey);
  EVP_MD_CTX_free(mdc);
  EVP_MD_CTX_free(mdc_tmp);

  return (ret);
}

#endif

/***
sign message with signcert and signpkey to create pkcs7 object

@function sign
@tparam string|bio msg
@tparam x509 signcert
@tparam evp_pkey signkey
@tparam[opt] stack_of_x509 cacerts
@tparam[opt=0] number flags
@treturn pkcs7 object
*/
static LUA_FUNCTION(openssl_pkcs7_sign)
{
  BIO *in  = load_bio_object(L, 1);
  X509 *cert = CHECK_OBJECT(2, X509, "openssl.x509");
  EVP_PKEY *privkey = CHECK_OBJECT(3, EVP_PKEY, "openssl.evp_pkey");
  STACK_OF(X509) *others = lua_isnoneornil(L, 4) ? 0 : openssl_sk_x509_fromtable(L, 4);
  long flags =  luaL_optint(L, 5, 0);
  PKCS7 *p7 = NULL;

  if (!X509_check_private_key(cert, privkey))
    luaL_error(L, "sigcert and private key not match");
  p7 = PKCS7_sign(cert, privkey, others, in, flags);
  BIO_free(in);
  if (others)
    sk_X509_pop_free(others, X509_free);
  if (p7)
  {
    PUSH_OBJECT(p7, "openssl.pkcs7");
    return 1;
  }
  else
  {
    luaL_error(L, "error creating PKCS7 structure!");
  }

  return 0;
}

/***
verify pkcs7 object, and return msg content, follow by singers

@function verify
@tparam pkcs7 in
@tparam[opt] stack_of_x509 signercerts
@tparam[opt] x509_store cacerts
@tparam[opt] string|bio msg
@tparam[opt=0] number flags
@treturn[1] string content
@treturn[1] boolean result
*/
static LUA_FUNCTION(openssl_pkcs7_verify)
{
  int ret = 0;
  PKCS7 *p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  STACK_OF(X509) *signers = lua_isnoneornil(L, 2) ? NULL : openssl_sk_x509_fromtable(L, 2);
  X509_STORE *store = lua_isnoneornil(L, 3) ? NULL : CHECK_OBJECT(3, X509_STORE, "openssl.x509_store");
  BIO* in = lua_isnoneornil(L, 4) ? NULL : load_bio_object(L, 4);
  long flags = luaL_optint(L, 5, PKCS7_DETACHED);
  BIO* out = NULL;

  if (!store)
    flags |= PKCS7_NOVERIFY;
  if ((flags&PKCS7_DETACHED) != 0)
    out = BIO_new(BIO_s_mem());

  if (PKCS7_verify(p7, signers, store, in, out, flags) == 1)
  {
    if (out && (flags & PKCS7_DETACHED) == 0)
    {
      BUF_MEM *bio_buf;

      BIO_get_mem_ptr(out, &bio_buf);
      lua_pushlstring(L, bio_buf->data, bio_buf->length);
    }
    else
    {
      lua_pushboolean(L, 1);
    }
    ret += 1;
  }
  else
  {
    ret = openssl_pushresult(L, 0);
  }
  if (signers)
    sk_X509_pop_free(signers, X509_free);
  if (out)
    BIO_free(out);
  if (in)
    BIO_free(in);
  return ret;
}

/***
encrypt message with recipcerts certificates return encrypted pkcs7 object

@function encrypt
@tparam string|bio msg
@tparam stack_of_x509 recipcerts
@tparam[opt='rc4'] string|evp_cipher cipher
@tparam[opt] number flags
*/
static LUA_FUNCTION(openssl_pkcs7_encrypt)
{
  PKCS7 * p7 = NULL;
  BIO *in = load_bio_object(L, 1);
  STACK_OF(X509) *recipcerts = openssl_sk_x509_fromtable(L, 2);
  const EVP_CIPHER *cipher = get_cipher(L, 3, "des3");
  long flags = luaL_optint(L, 4, 0);

  if (cipher == NULL)
  {
    luaL_error(L, "Failed to get cipher");
  }

  p7 = PKCS7_encrypt(recipcerts, in, cipher, flags);
  BIO_free(in);
  sk_X509_pop_free(recipcerts, X509_free);
  if (p7 == NULL)
  {
    lua_pushnil(L);
  }
  else
  {
    PUSH_OBJECT(p7, "openssl.pkcs7");
  }

  return 1;
}

/***
decrypt encrypted pkcs7 message

@function decrypt
@tparam pkcs7 input
@tparam x509 recipcert
@tparam evp_pkey recipkey
@treturn string decrypt message
*/
static LUA_FUNCTION(openssl_pkcs7_decrypt)
{
  PKCS7 *p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  X509 *cert = CHECK_OBJECT(2, X509, "openssl.x509");
  EVP_PKEY *key = CHECK_OBJECT(3, EVP_PKEY, "openssl.evp_pkey");
  long flags = luaL_optint(L, 4, 0);
  BIO *out = BIO_new(BIO_s_mem());

  if (PKCS7_decrypt(p7, key, cert, out, flags))
  {
    BUF_MEM* mem;
    BIO_get_mem_ptr(out, &mem);
    lua_pushlstring(L, mem->data, mem->length);
  }
  else
    lua_pushnil(L);
  BIO_free(out);
  return 1;
}

/***
openssl.pkcs7 object

@type pkcs7
*/
static LUA_FUNCTION(openssl_pkcs7_gc)
{
  PKCS7* p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  PKCS7_free(p7);
  return 0;
}

/***
export pkcs7 as string

@function export
@tparam[opt='pem'] string support export as 'pem' or 'der' format, default is 'pem'
@treturn string
*/
static LUA_FUNCTION(openssl_pkcs7_export)
{
  PKCS7 * p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  BIO* bio_out = NULL;
  int 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");

  bio_out  = BIO_new(BIO_s_mem());
  if (fmt == FORMAT_PEM)
  {

    if (PEM_write_bio_PKCS7(bio_out, p7))
    {
      BUF_MEM *bio_buf;
      BIO_get_mem_ptr(bio_out, &bio_buf);
      lua_pushlstring(L, bio_buf->data, bio_buf->length);
    }
    else
      lua_pushnil(L);
  }
  else
  {
    if (i2d_PKCS7_bio(bio_out, p7))
    {
      BUF_MEM *bio_buf;
      BIO_get_mem_ptr(bio_out, &bio_buf);
      lua_pushlstring(L, bio_buf->data, bio_buf->length);
    }
    else
      lua_pushnil(L);
  }

  BIO_free(bio_out);
  return 1;
}

static int openssl_push_pkcs7_signer_info(lua_State *L, PKCS7_SIGNER_INFO *info)
{
  lua_newtable(L);
  AUXILIAR_SET(L, -1, "version", ASN1_INTEGER_get(info->version), integer);

  if (info->issuer_and_serial != NULL)
  {
    X509_NAME *i = X509_NAME_dup(info->issuer_and_serial->issuer);
    ASN1_INTEGER *s = ASN1_INTEGER_dup(info->issuer_and_serial->serial);
    if (info->issuer_and_serial->issuer)
      AUXILIAR_SETOBJECT(L, i, "openssl.x509_name", -1, "issuer");

    if (info->issuer_and_serial->serial)
      AUXILIAR_SETOBJECT(L, s, "openssl.asn1_integer", -1, "serial");
  }

  if (info->digest_alg)
  {
    X509_ALGOR *dup = X509_ALGOR_dup(info->digest_alg);
    AUXILIAR_SETOBJECT(L, dup, "openssl.x509_algor", -1, "digest_alg");
  }
  if (info->digest_enc_alg)
  {
    X509_ALGOR *dup = X509_ALGOR_dup(info->digest_alg);
    AUXILIAR_SETOBJECT(L, dup, "openssl.x509_algor", -1, "digest_enc_alg");
  }
  if (info->enc_digest)
  {
    ASN1_STRING *dup = ASN1_STRING_dup(info->enc_digest);
    AUXILIAR_SETOBJECT(L, dup, "openssl.asn1_string", -1, "enc_digest");
  }

  if (info->pkey)
  {
    EVP_PKEY_up_ref(info->pkey);
    AUXILIAR_SETOBJECT(L, info->pkey, "openssl.evp_pkey", -1, "pkey");
  }
  return 1;
}

static LUA_FUNCTION(openssl_pkcs7_type)
{
  PKCS7 * p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  int i = OBJ_obj2nid(p7->type);

  lua_pushstring(L, OBJ_nid2sn(i));
  lua_pushstring(L, OBJ_nid2ln(i));
  return 2;
}

/***
export pkcs7 as a string

@function parse
@treturn table  a table has pkcs7 infomation, include type,and other things relate to types
*/
static LUA_FUNCTION(openssl_pkcs7_parse)
{
  PKCS7 * p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  STACK_OF(X509) *certs = NULL;
  STACK_OF(X509_CRL) *crls = NULL;
  int i = OBJ_obj2nid(p7->type);

  lua_newtable(L);
  AUXILIAR_SET(L, -1, "type", OBJ_nid2ln(i), string);
  switch (i)
  {
  case NID_pkcs7_signed:
  {
    PKCS7_SIGNED *sign = p7->d.sign;
    certs = sign->cert ? sign->cert : NULL;
    crls = sign->crl ? sign->crl : NULL;

    AUXILIAR_SET(L, -1, "version", ASN1_INTEGER_get(sign->version), integer);
    AUXILIAR_SET(L, -1, "detached", PKCS7_is_detached(p7), boolean);
    lua_pushstring(L, "md_algs");
    openssl_sk_x509_algor_totable(L, sign->md_algs);
    lua_rawset(L, -3);

    if (sign->signer_info)
    {
      int j, n;
      n = sk_PKCS7_SIGNER_INFO_num(sign->signer_info);
      lua_pushstring(L, "signer_info");
      lua_newtable(L);
      for (j = 0; j < n; j++)
      {
        PKCS7_SIGNER_INFO *info = sk_PKCS7_SIGNER_INFO_value(sign->signer_info, j);
        lua_pushinteger(L, j + 1);
        openssl_push_pkcs7_signer_info(L, info);
        lua_rawset(L, -3);
      }
      lua_rawset(L, -3);
    }

    if (!PKCS7_is_detached(p7))
    {
      PKCS7* c = sign->contents;
      c = PKCS7_dup(c);
      AUXILIAR_SETOBJECT(L, c, "openssl.pkcs7", -1, "contents");
    }
  }
  break;
  case NID_pkcs7_signedAndEnveloped:
    certs = p7->d.signed_and_enveloped->cert;
    crls = p7->d.signed_and_enveloped->crl;
    break;
  case NID_pkcs7_enveloped:
  {
    /*
    BIO * mem = BIO_new(BIO_s_mem());
    BIO * v_p7bio = PKCS7_dataDecode(p7,pkey,NULL,NULL);
    BUF_MEM *bptr = NULL;
    unsigned char src[4096];
    int len;

    while((len = BIO_read(v_p7bio,src,4096))>0){
     BIO_write(mem, src, len);
    }
    BIO_free(v_p7bio);
    BIO_get_mem_ptr(mem, &bptr);
    if((int)*puiDataLen < bptr->length)
    {
     *puiDataLen = bptr->length;
     ret = SAR_MemoryErr;
    }else{
     *puiDataLen =  bptr->length;
     memcpy(pucData,bptr->data, bptr->length);
    }
    */
  }
  break;
  case NID_pkcs7_digest:
  {
    PKCS7_DIGEST* d = p7->d.digest;
    PUSH_ASN1_OCTET_STRING(L, d->digest);
    lua_setfield(L, -2, "digest");
  }
  break;
  case NID_pkcs7_data:
  {
    PUSH_ASN1_OCTET_STRING(L, p7->d.data);
    lua_setfield(L, -2, "data");
  }
  break;
  default:
    break;
  }

  /* NID_pkcs7_signed or NID_pkcs7_signedAndEnveloped */
  if (certs != NULL)
  {
    lua_pushstring(L, "certs");
    openssl_sk_x509_totable(L, certs);
    lua_rawset(L, -3);
  }
  if (crls != NULL)
  {
    lua_pushstring(L, "crls");
    openssl_sk_x509_crl_totable(L, crls);
    lua_rawset(L, -3);
  }
  return 1;
}

/***
pkcs7 sign add signer

@function add_signer
@tparam x509 cert used to sign data
@tparam evp_pkey pkey used to sign data
@tparam evp_md|int digest method when sign data
@tparam[opt=0] int flags switch process when add signer
@treturn boolean result true for success
*/
static LUA_FUNCTION(openssl_pkcs7_sign_add_signer)
{
  PKCS7 *p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  X509 *signcert = CHECK_OBJECT(2, X509, "openssl.x509");
  EVP_PKEY *pkey = CHECK_OBJECT(3, EVP_PKEY, "openssl.evp_pkey");
  const EVP_MD* md = get_digest(L, 4, NULL);
  long flags = luaL_optint(L, 5, 0);
  PKCS7_SIGNER_INFO *signer = 0;

  luaL_argcheck(L, X509_check_private_key(signcert, pkey), 3,
                "sigcert and private key not match");

  signer = PKCS7_sign_add_signer(p7, signcert, pkey, md, flags);
  (void) signer;
  return openssl_pushresult(L, signcert != NULL ? 1 : 0);
}

#if OPENSSL_VERSION_NUMBER > 0x10000000L
/***
pkcs7 sign hash data
@function sign_digest
@tparam string data to sign data, maybe already hashed
@tparam[opt=0] int flags when sign data
@tparam[opt=false] boolean hashed when true will skip hash process
@treturn boolean result true for success
*/
static LUA_FUNCTION(openssl_pkcs7_sign_digest)
{
  PKCS7 *p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  size_t l;
  const char* data = luaL_checklstring(L, 2, &l);
  long flags = luaL_optint(L, 3, 0);
  int hash = lua_isnoneornil(L, 4) ? 0 : lua_toboolean(L, 4);

  int ret = 0;
  int i, j;

  const EVP_MD* md;
  PKCS7_SIGNER_INFO *si;
  EVP_MD_CTX *mdc;
  STACK_OF(X509_ATTRIBUTE) *sk;
  STACK_OF(PKCS7_SIGNER_INFO) *si_sk = NULL;
  ASN1_OCTET_STRING *os = NULL;

  if (p7->d.ptr == NULL)
  {
    luaL_error(L, "pkcs7 without content");
    return 0;
  }

  if (flags & PKCS7_DETACHED)
  {
    PKCS7_set_detached(p7, 1);
  }

  mdc = EVP_MD_CTX_new();
  EVP_MD_CTX_init(mdc);
  i = OBJ_obj2nid(p7->type);
  p7->state = PKCS7_S_HEADER;

  switch (i)
  {
  case NID_pkcs7_data:
    os = p7->d.data;
    break;
  case NID_pkcs7_signedAndEnveloped:
    /* XXXXXXXXXXXXXXXX */
    si_sk = p7->d.signed_and_enveloped->signer_info;
    os = p7->d.signed_and_enveloped->enc_data->enc_data;
    if (!os)
    {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
      os = M_ASN1_OCTET_STRING_new();
#else
      os = ASN1_OCTET_STRING_new();
#endif
      if (!os)
      {
        PKCS7err(PKCS7_F_PKCS7_DATAFINAL, ERR_R_MALLOC_FAILURE);
        goto err;
      }
      p7->d.signed_and_enveloped->enc_data->enc_data = os;
    }
    break;
  case NID_pkcs7_enveloped:
    /* XXXXXXXXXXXXXXXX */
    os = p7->d.enveloped->enc_data->enc_data;
    if (!os)
    {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
      os = M_ASN1_OCTET_STRING_new();
#else
      os = ASN1_OCTET_STRING_new();
#endif
      if (!os)
      {
        PKCS7err(PKCS7_F_PKCS7_DATAFINAL, ERR_R_MALLOC_FAILURE);
        goto err;
      }
      p7->d.enveloped->enc_data->enc_data = os;
    }
    break;
  case NID_pkcs7_signed:
    si_sk = p7->d.sign->signer_info;
    os = PKCS7_get_octet_string(p7->d.sign->contents);
    /* If detached data then the content is excluded */
    if (PKCS7_type_is_data(p7->d.sign->contents) && p7->detached)
    {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
      M_ASN1_OCTET_STRING_free(os);
#else
      ASN1_OCTET_STRING_free(os);
#endif
      os = NULL;
      p7->d.sign->contents->d.data = NULL;
    }
    break;

  case NID_pkcs7_digest:
    os = PKCS7_get_octet_string(p7->d.digest->contents);
    /* If detached data then the content is excluded */
    if (PKCS7_type_is_data(p7->d.digest->contents) && p7->detached)
    {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
      M_ASN1_OCTET_STRING_free(os);
#else
      ASN1_OCTET_STRING_free(os);
#endif
      os = NULL;
      p7->d.digest->contents->d.data = NULL;
    }
    break;

  default:
    PKCS7err(PKCS7_F_PKCS7_DATAFINAL, PKCS7_R_UNSUPPORTED_CONTENT_TYPE);
    goto err;
  }

  if (si_sk != NULL)
  {
    for (i = 0; i < sk_PKCS7_SIGNER_INFO_num(si_sk); i++)
    {
      si = sk_PKCS7_SIGNER_INFO_value(si_sk, i);
      if (si->pkey == NULL)
        continue;
      j = OBJ_obj2nid(si->digest_alg->algorithm);
      md = EVP_get_digestbynid(j);
      EVP_DigestInit_ex(mdc, md, NULL);

      if (hash)
      {
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
        if (l == (size_t) mdc->digest->ctx_size)
        {
          memcpy(mdc->md_data, data, l);
        }
#else
        if (l == (size_t)EVP_MD_meth_get_app_datasize(EVP_MD_CTX_md(mdc)))
        {
          memcpy(EVP_MD_CTX_md_data(mdc), data, l);
        }
#endif
        else
        {
          EVP_MD_CTX_free(mdc);
          luaL_argerror(L, 2, "data with wrong length");
        }
      }
      else
        EVP_DigestUpdate(mdc, data, l);

      sk = si->auth_attr;

      /*
      * If there are attributes, we add the digest attribute and only
      * sign the attributes
      */
      if (sk_X509_ATTRIBUTE_num(sk) > 0)
      {
        if (!do_pkcs7_signed_attrib(si, mdc))
          goto err;
      }
      else
      {
        unsigned char *abuf = NULL;
        unsigned int abuflen;
        abuflen = EVP_PKEY_size(si->pkey);
        abuf = OPENSSL_malloc(abuflen);
        if (!abuf)
          goto err;

        if (!EVP_SignFinal(mdc, abuf, &abuflen, si->pkey))
        {
          PKCS7err(PKCS7_F_PKCS7_DATAFINAL, ERR_R_EVP_LIB);
          goto err;
        }
        ASN1_STRING_set0(si->enc_digest, abuf, abuflen);
      }
    }
  }
  else if (i == NID_pkcs7_digest)
  {
    unsigned char md_data[EVP_MAX_MD_SIZE];
    unsigned int md_len;
    md = EVP_get_digestbynid(OBJ_obj2nid(p7->d.digest->md->algorithm));
    EVP_DigestInit_ex(mdc, md, NULL);
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
    if (l == (size_t) mdc->digest->ctx_size)
    {
      memcpy(mdc->md_data, data, l);
    }
#else
    if (l == (size_t)EVP_MD_meth_get_app_datasize(EVP_MD_CTX_md(mdc)))
    {
      memcpy(EVP_MD_CTX_md_data(mdc), data, l);
    }
#endif
    else
    {
      EVP_MD_CTX_free(mdc);
      luaL_error(L, "data with wrong data");
    }
    if (!EVP_DigestFinal_ex(mdc, md_data, &md_len))
      goto err;
    ASN1_OCTET_STRING_set(p7->d.digest->digest, md_data, md_len);
  }

  if (!PKCS7_is_detached(p7))
  {
    /*
    * NOTE(emilia): I think we only reach os == NULL here because detached
    * digested data support is broken.
    */
    if (os == NULL)
      goto err;
    if (!(os->flags & ASN1_STRING_FLAG_NDEF))
    {
      char *cont = memdup(data, l);
      long contlen = l;
      ASN1_STRING_set0(os, (unsigned char *) cont, contlen);
    }
  }

  ret = 1;
err:
  EVP_MD_CTX_free(mdc);
  return openssl_pushresult(L, ret);
}

/***
pkcs7 verify signature or digest

@function verify_digest
@tparam[opt] table certs contains certificate used to sign data
@tparam[opt] x509_store store to verify certs
@tparam string data to be signed
@tparam[opt=false] boolean hashed true for data already hashed
@treturn boolean result true for success
*/
static LUA_FUNCTION(openssl_pkcs7_verify_digest)
{
  PKCS7 *p7 = CHECK_OBJECT(1, PKCS7, "openssl.pkcs7");
  STACK_OF(X509) *certs = lua_isnoneornil(L, 2) ? NULL : openssl_sk_x509_fromtable(L, 2);
  X509_STORE *store = lua_isnoneornil(L, 3) ? NULL : CHECK_OBJECT(3, X509_STORE, "openssl.x509_store");
  size_t len = 0;
  const char* data = luaL_optlstring(L, 4, NULL, &len);
  long flags = luaL_optint(L, 5, 0);
  int hash = lua_isnoneornil(L, 6) ? 0 : lua_toboolean(L, 6);

  STACK_OF(X509) *signers;
  X509 *signer;
  STACK_OF(PKCS7_SIGNER_INFO) *sinfos;
  PKCS7_SIGNER_INFO *si;
  X509_STORE_CTX *cert_ctx;

  int i, j = 0, k, ret = 0;

  /* Check for no data and no content: no data to verify signature */
  if (flags & PKCS7_DETACHED)
  {
    if (!PKCS7_get_detached(p7))
    {
      luaL_error(L, "pkcs7 must be detached signedData");
    }
    luaL_argcheck(L, data != NULL, 4, "need data to be verified");
  }
  else
  {
    ASN1_OCTET_STRING *os = NULL;

    luaL_argcheck(L, data == NULL, 4, "must be nil or none");
    os = PKCS7_get_octet_string(p7->d.sign->contents);
    data = (const char*)os->data;
    len = os->length;
  }

  sinfos = PKCS7_get_signer_info(p7);
  if (!sinfos || !sk_PKCS7_SIGNER_INFO_num(sinfos))
  {
    luaL_error(L, "pkcs7 signedData without signature");
  }

  signers = PKCS7_get0_signers(p7, certs, flags);
  if (!signers)
  {
    luaL_error(L, "pkcs7 signedData without signers");
  }

  if (!store)
    flags |= PKCS7_NOVERIFY;

  /* Now verify the certificates */
  if (!(flags & PKCS7_NOVERIFY))
    for (k = 0; k < sk_X509_num(signers); k++)
    {
      signer = sk_X509_value(signers, k);
      cert_ctx = X509_STORE_CTX_new();
      if (!(flags & PKCS7_NOCHAIN))
      {
        if (!X509_STORE_CTX_init(cert_ctx, store, signer,
                                 p7->d.sign->cert))
        {
          PKCS7err(PKCS7_F_PKCS7_VERIFY, ERR_R_X509_LIB);
          goto err;
        }
        X509_STORE_CTX_set_default(cert_ctx, "smime_sign");
      }
      else if (!X509_STORE_CTX_init(cert_ctx, store, signer, NULL))
      {
        PKCS7err(PKCS7_F_PKCS7_VERIFY, ERR_R_X509_LIB);
        goto err;
      }
      if (!(flags & PKCS7_NOCRL))
        X509_STORE_CTX_set0_crls(cert_ctx, p7->d.sign->crl);
      i = X509_verify_cert(cert_ctx);
      if (i <= 0)
        j = X509_STORE_CTX_get_error(cert_ctx);
      X509_STORE_CTX_free(cert_ctx);
      if (i <= 0)
      {
        PKCS7err(PKCS7_F_PKCS7_VERIFY,
                 PKCS7_R_CERTIFICATE_VERIFY_ERROR);
        ERR_add_error_data(2, "Verify error:",
                           X509_verify_cert_error_string(j));
        goto err;
      }
      /* Check for revocation status here */
    }

  /* Now Verify All Signatures */
  if (!(flags & PKCS7_NOSIGS))
    for (i = 0; i < sk_PKCS7_SIGNER_INFO_num(sinfos); i++)
    {
      si = sk_PKCS7_SIGNER_INFO_value(sinfos, i);
      signer = sk_X509_value(signers, i);
      j = PKCS7_signatureVerify_digest(p7, si, signer,
                                       (const unsigned char*) data, len, hash);
      if (j <= 0)
      {
        PKCS7err(PKCS7_F_PKCS7_VERIFY, PKCS7_R_SIGNATURE_FAILURE);
        goto err;
      }
    }
  ret = 1;

err:
  if (certs)
    sk_X509_pop_free(certs, X509_free);
  sk_X509_free(signers);
  return openssl_pushresult(L, ret);
}
#endif
/***
verify pkcs7 object, and return msg content, follow by singers

@function verify
@tparam[opt] stack_of_x509 signercerts
@tparam[opt] x509_store cacerts
@tparam[opt] string|bio msg
@tparam[opt=0] number flags
@treturn string content
@treturn stack_of_x509 signers
*/

/***
decrypt encrypted pkcs7 message

@function decrypt
@tparam x509 recipcert
@tparam evp_pkey recipkey
@treturn string decrypt message
*/

static luaL_Reg pkcs7_funcs[] =
{
  {"type",          openssl_pkcs7_type},
  {"parse",         openssl_pkcs7_parse},
  {"export",        openssl_pkcs7_export},
  {"decrypt",       openssl_pkcs7_decrypt},
  {"verify",        openssl_pkcs7_verify},
  {"final",         openssl_pkcs7_final},
#if OPENSSL_VERSION_NUMBER > 0x10000000L
  {"add_signer",    openssl_pkcs7_sign_add_signer},
  {"add",           openssl_pkcs7_add},
  {"sign_digest",   openssl_pkcs7_sign_digest},
  {"verify_digest", openssl_pkcs7_verify_digest},
#endif

  {"__gc",          openssl_pkcs7_gc},
  {"__tostring",    auxiliar_tostring},

  {NULL,      NULL}
};

static const luaL_Reg R[] =
{
#if OPENSSL_VERSION_NUMBER > 0x10000000L
  {"new",         openssl_pkcs7_new},
#endif
  {"read",        openssl_pkcs7_read},
  {"sign",        openssl_pkcs7_sign},
  {"verify",      openssl_pkcs7_verify},
  {"encrypt",     openssl_pkcs7_encrypt},
  {"decrypt",     openssl_pkcs7_decrypt},

  {NULL,  NULL}
};

static LuaL_Enumeration pkcs7_const[] =
{
  {"TEXT",         PKCS7_TEXT},
  {"NOCERTS",      PKCS7_NOCERTS},
  {"NOSIGS",       PKCS7_NOSIGS},
  {"NOCHAIN",      PKCS7_NOCHAIN},
  {"NOINTERN",     PKCS7_NOINTERN},
  {"NOVERIFY",     PKCS7_NOVERIFY},
  {"DETACHED",     PKCS7_DETACHED},
  {"BINARY",       PKCS7_BINARY},
  {"NOATTR",       PKCS7_NOATTR},
  {"NOSMIMECAP",   PKCS7_NOSMIMECAP},
  {"NOOLDMIMETYPE", PKCS7_NOOLDMIMETYPE},
  {"CRLFEOL",      PKCS7_CRLFEOL},
  {"STREAM",       PKCS7_STREAM},
  {"NOCRL",        PKCS7_NOCRL},
  {"PARTIAL",      PKCS7_PARTIAL},
  {"REUSE_DIGEST", PKCS7_REUSE_DIGEST},

  {NULL,           0}
};

int luaopen_pkcs7(lua_State *L)
{
  auxiliar_newclass(L, "openssl.pkcs7", pkcs7_funcs);

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

  lua_pushliteral(L, "version");    /** version */
  lua_pushliteral(L, MYVERSION);
  lua_settable(L, -3);

  auxiliar_enumerate(L, -1, pkcs7_const);
  return 1;
}
