/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-2015 Facebook, Inc. (http://www.facebook.com)     |
   | Copyright (c) 1997-2010 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/

#include "hphp/runtime/ext/openssl/ext_openssl.h"
#include "hphp/runtime/base/ssl-socket.h"
#include "hphp/runtime/base/string-util.h"
#include "hphp/runtime/base/zend-string.h"
#include "hphp/system/constants.h"
#include "hphp/util/logger.h"

#include <openssl/conf.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/opensslv.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <vector>

namespace HPHP {

#define MIN_KEY_LENGTH          384

// bitfields
const int64_t k_OPENSSL_RAW_DATA = 1;
const int64_t k_OPENSSL_ZERO_PADDING = 2;
const int64_t k_OPENSSL_NO_PADDING = 3;
const int64_t k_OPENSSL_PKCS1_OAEP_PADDING = 4;

// exported constants
const int64_t k_OPENSSL_SSLV23_PADDING = 2;
const int64_t k_OPENSSL_PKCS1_PADDING = 1;

static char default_ssl_conf_filename[PATH_MAX];

class OpenSSLInitializer {
public:
  OpenSSLInitializer() {
    SSL_library_init();
    OpenSSL_add_all_ciphers();
    OpenSSL_add_all_digests();
    OpenSSL_add_all_algorithms();

    ERR_load_ERR_strings();
    ERR_load_crypto_strings();
    ERR_load_EVP_strings();

    /* Determine default SSL configuration file */
    char *config_filename = getenv("OPENSSL_CONF");
    if (config_filename == NULL) {
      config_filename = getenv("SSLEAY_CONF");
    }

    /* default to 'openssl.cnf' if no environment variable is set */
    if (config_filename == NULL) {
      snprintf(default_ssl_conf_filename, sizeof(default_ssl_conf_filename),
               "%s/%s", X509_get_default_cert_area(), "openssl.cnf");
    } else {
      always_assert(
        strlen(config_filename) < sizeof(default_ssl_conf_filename));
      strcpy(default_ssl_conf_filename, config_filename);
    }
  }

  ~OpenSSLInitializer() {
    EVP_cleanup();
  }
};
static OpenSSLInitializer s_openssl_initializer;

///////////////////////////////////////////////////////////////////////////////
// resource classes

class Key : public SweepableResourceData {
public:
  EVP_PKEY *m_key;
  explicit Key(EVP_PKEY *key) : m_key(key) { assert(m_key);}
  ~Key() {
    if (m_key) EVP_PKEY_free(m_key);
  }

  CLASSNAME_IS("OpenSSL key");
  // overriding ResourceData
  const String& o_getClassNameHook() const override { return classnameof(); }

  DECLARE_RESOURCE_ALLOCATION(Key)

  bool isPrivate() {
    assert(m_key);
    switch (m_key->type) {
#ifndef NO_RSA
    case EVP_PKEY_RSA:
    case EVP_PKEY_RSA2:
      assert(m_key->pkey.rsa);
      if (!m_key->pkey.rsa->p || !m_key->pkey.rsa->q) {
        return false;
      }
      break;
#endif
#ifndef NO_DSA
    case EVP_PKEY_DSA:
    case EVP_PKEY_DSA1:
    case EVP_PKEY_DSA2:
    case EVP_PKEY_DSA3:
    case EVP_PKEY_DSA4:
      assert(m_key->pkey.dsa);
      if (!m_key->pkey.dsa->p || !m_key->pkey.dsa->q ||
          !m_key->pkey.dsa->priv_key) {
        return false;
      }
      break;
#endif
#ifndef NO_DH
    case EVP_PKEY_DH:
      assert(m_key->pkey.dh);
      if (!m_key->pkey.dh->p || !m_key->pkey.dh->priv_key) {
        return false;
      }
      break;
#endif
    default:
      raise_warning("key type not supported in this PHP build!");
      break;
    }
    return true;
  }

  /**
   * Given a variant, coerce it into a EVP_PKEY object. It can be:
   *
   *   1. private key resource from openssl_get_privatekey()
   *   2. X509 resource -> public key will be extracted from it
   *   3. if it starts with file:// interpreted as path to key file
   *   4. interpreted as the data from the cert/key file and interpreted in
   *      same way as openssl_get_privatekey()
   *   5. an array(0 => [items 2..4], 1 => passphrase)
   *   6. if val is a string (possibly starting with file:///) and it is not
   *      an X509 certificate, then interpret as public key
   *
   * NOTE: If you are requesting a private key but have not specified a
   *   passphrase, you should use an empty string rather than NULL for the
   *   passphrase - NULL causes a passphrase prompt to be emitted in
   *   the Apache error log!
   */
  static req::ptr<Key> Get(const Variant& var, bool public_key,
                           const char *passphrase = nullptr) {
    if (var.isArray()) {
      Array arr = var.toArray();
      if (!arr.exists(int64_t(0)) || !arr.exists(int64_t(1))) {
        raise_warning("key array must be of the form "
                        "array(0 => key, 1 => phrase)");
        return nullptr;
      }

      String zphrase = arr[1].toString();
      return GetHelper(arr[0], public_key, zphrase.data());
    }
    return GetHelper(var, public_key, passphrase);
  }

  static req::ptr<Key> GetHelper(const Variant& var, bool public_key,
                                 const char *passphrase) {
    req::ptr<Certificate> ocert;
    EVP_PKEY *key = NULL;

    if (var.isResource()) {
      auto cert = dyn_cast_or_null<Certificate>(var);
      auto key = dyn_cast_or_null<Key>(var);
      if (!cert && !key) return nullptr;
      if (key) {
        bool is_priv = key->isPrivate();
        if (!public_key && !is_priv) {
          raise_warning("supplied key param is a public key");
          return nullptr;
        }
        if (public_key && is_priv) {
          raise_warning("Don't know how to get public key from "
                          "this private key");
          return nullptr;
        }
        return key;
      }
      ocert = cert;
    } else {
      /* it's an X509 file/cert of some kind, and we need to extract
         the data from that */
      if (public_key) {
        ocert = Certificate::Get(var);
        if (!ocert) {
          /* not a X509 certificate, try to retrieve public key */
          BIO *in = Certificate::ReadData(var);
          if (in == nullptr) return nullptr;
          key = PEM_read_bio_PUBKEY(in, NULL,NULL, NULL);
          BIO_free(in);
        }
      } else {
        /* we want the private key */
        BIO *in = Certificate::ReadData(var);
        if (in == nullptr) return nullptr;
        key = PEM_read_bio_PrivateKey(in, NULL,NULL, (void*)passphrase);
        BIO_free(in);
      }
    }

    if (public_key && ocert && key == nullptr) {
      /* extract public key from X509 cert */
      key = (EVP_PKEY *)X509_get_pubkey(ocert->get());
    }

    if (key) {
      return req::make<Key>(key);
    }

    return nullptr;
  }
};

IMPLEMENT_RESOURCE_ALLOCATION(Key)

/**
 * Certificate Signing Request
 */
class CSRequest : public SweepableResourceData {
  X509_REQ *m_csr;

public:
  explicit CSRequest(X509_REQ *csr) : m_csr(csr) {
    assert(m_csr);
  }

  X509_REQ *csr() { return m_csr; }

  ~CSRequest() {
    // X509_REQ_free(nullptr) is a no-op
    X509_REQ_free(m_csr);
  }

  CLASSNAME_IS("OpenSSL X.509 CSR");
  // overriding ResourceData
  const String& o_getClassNameHook() const override { return classnameof(); }

  DECLARE_RESOURCE_ALLOCATION(CSRequest)

  static req::ptr<CSRequest> Get(const Variant& var) {
    auto csr = cast_or_null<CSRequest>(GetRequest(var));
    if (!csr || !csr->m_csr) {
      raise_warning("cannot get CSR");
      return NULL;
    }
    return csr;
  }

private:
  static req::ptr<CSRequest> GetRequest(const Variant& var) {
    if (var.isResource()) {
      return dyn_cast_or_null<CSRequest>(var);
    }
    if (var.isString() || var.isObject()) {
      BIO *in = Certificate::ReadData(var);
      if (in == nullptr) return nullptr;

      X509_REQ *csr = PEM_read_bio_X509_REQ(in, NULL,NULL,NULL);
      BIO_free(in);
      if (csr) {
        return req::make<CSRequest>(csr);
      }
    }
    return nullptr;
  }
};

IMPLEMENT_RESOURCE_ALLOCATION(CSRequest)

class php_x509_request {
public:
#if OPENSSL_VERSION_NUMBER >= 0x10000002L
  LHASH_OF(CONF_VALUE) * global_config; /* Global SSL config */
  LHASH_OF(CONF_VALUE) * req_config;    /* SSL config for this request */
#else
  LHASH *global_config;  /* Global SSL config */
  LHASH *req_config;     /* SSL config for this request */
#endif
  const EVP_MD *md_alg;
  const EVP_MD *digest;
  const char *section_name;
  const char *config_filename;
  const char *digest_name;
  const char *extensions_section;
  const char *request_extensions_section;
  int priv_key_bits;
  int priv_key_type;
  int priv_key_encrypt;
  EVP_PKEY *priv_key;

  static bool load_rand_file(const char *file, int *egdsocket, int *seeded) {
    char buffer[PATH_MAX];

    *egdsocket = 0;
    *seeded = 0;
    if (file == NULL) {
      file = RAND_file_name(buffer, sizeof(buffer));
#ifndef OPENSSL_NO_RAND_EGD
    } else if (RAND_egd(file) > 0) {
      /* if the given filename is an EGD socket, don't
       * write anything back to it */
      *egdsocket = 1;
      return true;
#endif
    }

    if (file == NULL || !RAND_load_file(file, -1)) {
      if (RAND_status() == 0) {
        raise_warning("unable to load random state; not enough data!");
        return false;
      }
      return false;
    }
    *seeded = 1;
    return true;
  }

  static bool write_rand_file(const char *file, int egdsocket, int seeded) {
    char buffer[PATH_MAX];

    if (egdsocket || !seeded) {
      /* if we did not manage to read the seed file, we should not write
       * a low-entropy seed file back */
      return false;
    }
    if (file == NULL) {
      file = RAND_file_name(buffer, sizeof(buffer));
    }
    if (file == NULL || !RAND_write_file(file)) {
      raise_warning("unable to write random state");
      return false;
    }
    return true;
  }

  bool generatePrivateKey() {
    assert(priv_key == NULL);

    if (priv_key_bits < MIN_KEY_LENGTH) {
      raise_warning("private key length is too short; it needs to be "
                      "at least %d bits, not %d",
                      MIN_KEY_LENGTH, priv_key_bits);
      return false;
    }

    char *randfile = CONF_get_string(req_config, section_name, "RANDFILE");
    int egdsocket, seeded;
    load_rand_file(randfile, &egdsocket, &seeded);

    bool ret = false;
    if ((priv_key = EVP_PKEY_new()) != NULL) {
      switch (priv_key_type) {
      case OPENSSL_KEYTYPE_RSA:
        if (EVP_PKEY_assign_RSA
            (priv_key, RSA_generate_key(priv_key_bits, 0x10001, NULL, NULL))) {
          ret = true;
        }
        break;
#if !defined(NO_DSA) && defined(HAVE_DSA_DEFAULT_METHOD)
      case OPENSSL_KEYTYPE_DSA:
        {
          DSA *dsapar = DSA_generate_parameters(priv_key_bits, NULL, 0, NULL,
                                                NULL, NULL, NULL);
          if (dsapar) {
            DSA_set_method(dsapar, DSA_get_default_method());
            if (DSA_generate_key(dsapar)) {
              if (EVP_PKEY_assign_DSA(priv_key, dsapar)) {
                ret = true;
              }
            } else {
              DSA_free(dsapar);
            }
          }
        }
        break;
#endif
      default:
        raise_warning("Unsupported private key type");
      }
    }

    write_rand_file(randfile, egdsocket, seeded);
    return ret;
  }
};

///////////////////////////////////////////////////////////////////////////////
// utilities

static void add_assoc_name_entry(Array &ret, const char *key,
                                 X509_NAME *name, bool shortname) {
  Array subitem_data;
  Array &subitem = key ? subitem_data : ret;

  for (int i = 0; i < X509_NAME_entry_count(name); i++) {
    X509_NAME_ENTRY *ne = X509_NAME_get_entry(name, i);
    ASN1_OBJECT *obj = X509_NAME_ENTRY_get_object(ne);
    int nid = OBJ_obj2nid(obj);
    int obj_cnt = 0;

    char *sname;
    if (shortname) {
      sname = (char *)OBJ_nid2sn(nid);
    } else {
      sname = (char *)OBJ_nid2ln(nid);
    }

    Array subentries;
    int last = -1;
    int j;
    ASN1_STRING *str = NULL;
    unsigned char *to_add = NULL;
    int to_add_len = 0;
    for (;;) {
      j = X509_NAME_get_index_by_OBJ(name, obj, last);
      if (j < 0) {
        if (last != -1) break;
      } else {
        obj_cnt++;
        ne  = X509_NAME_get_entry(name, j);
        str = X509_NAME_ENTRY_get_data(ne);
        if (ASN1_STRING_type(str) != V_ASN1_UTF8STRING) {
          to_add_len = ASN1_STRING_to_UTF8(&to_add, str);
          if (to_add_len != -1) {
            subentries.append(String((char*)to_add, to_add_len, AttachString));
          }
        } else {
          to_add = ASN1_STRING_data(str);
          to_add_len = ASN1_STRING_length(str);
          subentries.append(String((char*)to_add, to_add_len, CopyString));
        }
      }
      last = j;
    }
    i = last;

    if (obj_cnt > 1) {
      subitem.set(String(sname, CopyString), subentries);
    } else if (obj_cnt && str && to_add_len > -1) {
      // Use the string instance we created above.
      subitem.set(String(sname, CopyString), subentries[0]);
    }
  }

  if (key) {
    ret.set(String(key, CopyString), subitem);
  }
}

static const char *read_string(const Array& args, const String& key, const char *def,
                               std::vector<String> &strings) {
  if (args.exists(key)) {
    String value = args[key].toString();
    strings.push_back(value);
    return (char*)value.data();
  }
  return def;
}

static int64_t read_integer(const Array& args, const String& key, int64_t def) {
  if (args.exists(key)) {
    return args[key].toInt64();
  }
  return def;
}

static bool add_oid_section(struct php_x509_request *req) {
  char *str = CONF_get_string(req->req_config, NULL, "oid_section");
  if (str == NULL) {
    return true;
  }

  STACK_OF(CONF_VALUE) *sktmp = CONF_get_section(req->req_config, str);
  if (sktmp == NULL) {
    raise_warning("problem loading oid section %s", str);
    return false;
  }

  for (int i = 0; i < sk_CONF_VALUE_num(sktmp); i++) {
    CONF_VALUE *cnf = sk_CONF_VALUE_value(sktmp, i);
    if (OBJ_create(cnf->value, cnf->name, cnf->name) == NID_undef) {
      raise_warning("problem creating object %s=%s", cnf->name, cnf->value);
      return false;
    }
  }
  return true;
}

#if OPENSSL_VERSION_NUMBER >= 0x10000002L
static inline bool php_openssl_config_check_syntax
(const char *section_label, const char *config_filename, const char *section,
 LHASH_OF(CONF_VALUE) *config) {
#else
static inline bool php_openssl_config_check_syntax
(const char *section_label, const char *config_filename, const char *section,
 LHASH *config) {
#endif

  X509V3_CTX ctx;
  X509V3_set_ctx_test(&ctx);
  X509V3_set_conf_lhash(&ctx, config);
  if (!X509V3_EXT_add_conf(config, &ctx, (char*)section, NULL)) {
    raise_warning("Error loading %s section %s of %s",
                    section_label, section, config_filename);
    return false;
  }
  return true;
}

const StaticString
  s_config("config"),
  s_config_section_name("config_section_name"),
  s_digest_alg("digest_alg"),
  s_x509_extensions("x509_extensions"),
  s_req_extensions("req_extensions"),
  s_private_key_bits("private_key_bits"),
  s_private_key_type("private_key_type"),
  s_encrypt_key("encrypt_key");

static bool php_openssl_parse_config(struct php_x509_request *req,
                                     const Array& args,
                                     std::vector<String> &strings) {
  req->config_filename =
    read_string(args, s_config, default_ssl_conf_filename, strings);
  req->section_name =
    read_string(args, s_config_section_name, "req", strings);
  req->global_config = CONF_load(NULL, default_ssl_conf_filename, NULL);
  req->req_config = CONF_load(NULL, req->config_filename, NULL);
  if (req->req_config == NULL) {
    return false;
  }

  /* read in the oids */
  char *str = CONF_get_string(req->req_config, NULL, "oid_file");
  if (str) {
    BIO *oid_bio = BIO_new_file(str, "r");
    if (oid_bio) {
      OBJ_create_objects(oid_bio);
      BIO_free(oid_bio);
    }
  }
  if (!add_oid_section(req)) {
    return false;
  }

  req->digest_name =
    read_string(args, s_digest_alg,
                CONF_get_string(req->req_config, req->section_name,
                                "default_md"),
                strings);

  req->extensions_section =
    read_string(args, s_x509_extensions,
                CONF_get_string(req->req_config, req->section_name,
                                "x509_extensions"),
                strings);

  req->request_extensions_section =
    read_string(args, s_req_extensions,
                CONF_get_string(req->req_config, req->section_name,
                                "req_extensions"),
                strings);

  req->priv_key_bits =
    read_integer(args, s_private_key_bits,
                 CONF_get_number(req->req_config, req->section_name,
                                 "default_bits"));

  req->priv_key_type =
    read_integer(args, s_private_key_type, OPENSSL_KEYTYPE_DEFAULT);

  if (args.exists(s_encrypt_key)) {
    bool value = args[s_encrypt_key].toBoolean();
    req->priv_key_encrypt = value ? 1 : 0;
  } else {
    str = CONF_get_string(req->req_config, req->section_name,
                          "encrypt_rsa_key");
    if (str == NULL) {
      str = CONF_get_string(req->req_config, req->section_name, "encrypt_key");
    }
    if (str && strcmp(str, "no") == 0) {
      req->priv_key_encrypt = 0;
    } else {
      req->priv_key_encrypt = 1;
    }
  }

  /* digest alg */
  if (req->digest_name == NULL) {
    req->digest_name = CONF_get_string(req->req_config, req->section_name,
                                       "default_md");
  }
  if (req->digest_name) {
    req->digest = req->md_alg = EVP_get_digestbyname(req->digest_name);
  }
  if (req->md_alg == NULL) {
    req->md_alg = req->digest = EVP_md5();
  }

  if (req->extensions_section &&
      !php_openssl_config_check_syntax
      ("extensions_section", req->config_filename, req->extensions_section,
       req->req_config)) {
    return false;
  }

  /* set the string mask */
  str = CONF_get_string(req->req_config, req->section_name, "string_mask");
  if (str && !ASN1_STRING_set_default_mask_asc(str)) {
    raise_warning("Invalid global string mask setting %s", str);
    return false;
  }

  if (req->request_extensions_section &&
      !php_openssl_config_check_syntax
      ("request_extensions_section", req->config_filename,
       req->request_extensions_section, req->req_config)) {
    return false;
  }

  return true;
}

static void php_openssl_dispose_config(struct php_x509_request *req) {
  if (req->global_config) {
    CONF_free(req->global_config);
    req->global_config = NULL;
  }
  if (req->req_config) {
    CONF_free(req->req_config);
    req->req_config = NULL;
  }
}

static STACK_OF(X509) *load_all_certs_from_file(const char *certfile) {
  STACK_OF(X509_INFO) *sk = NULL;
  STACK_OF(X509) *stack = NULL, *ret = NULL;
  BIO *in = NULL;
  X509_INFO *xi;

  if (!(stack = sk_X509_new_null())) {
    raise_warning("memory allocation failure");
    goto end;
  }

  if (!(in = BIO_new_file(certfile, "r"))) {
    raise_warning("error opening the file, %s", certfile);
    sk_X509_free(stack);
    goto end;
  }

  /* This loads from a file, a stack of x509/crl/pkey sets */
  if (!(sk = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL))) {
    raise_warning("error reading the file, %s", certfile);
    sk_X509_free(stack);
    goto end;
  }

  /* scan over it and pull out the certs */
  while (sk_X509_INFO_num(sk)) {
    xi = sk_X509_INFO_shift(sk);
    if (xi->x509 != NULL) {
      sk_X509_push(stack, xi->x509);
      xi->x509 = NULL;
    }
    X509_INFO_free(xi);
  }
  if (!sk_X509_num(stack)) {
    raise_warning("no certificates in file, %s", certfile);
    sk_X509_free(stack);
    goto end;
  }
  ret = stack;

end:
  BIO_free(in);
  sk_X509_INFO_free(sk);

  return ret;
}

/**
 * calist is an array containing file and directory names.  create a
 * certificate store and add those certs to it for use in verification.
 */
static X509_STORE *setup_verify(const Array& calist) {
  X509_STORE *store = X509_STORE_new();
  if (store == NULL) {
    return NULL;
  }

  X509_LOOKUP *dir_lookup, *file_lookup;
  int ndirs = 0, nfiles = 0;
  for (ArrayIter iter(calist); iter; ++iter) {
    String item = iter.second().toString();

    struct stat sb;
    if (stat(item.data(), &sb) == -1) {
      raise_warning("unable to stat %s", item.data());
      continue;
    }

    if ((sb.st_mode & S_IFREG) == S_IFREG) {
      file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
      if (file_lookup == NULL ||
          !X509_LOOKUP_load_file(file_lookup, item.data(),
                                 X509_FILETYPE_PEM)) {
        raise_warning("error loading file %s", item.data());
      } else {
        nfiles++;
      }
      file_lookup = NULL;
    } else {
      dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
      if (dir_lookup == NULL ||
          !X509_LOOKUP_add_dir(dir_lookup, item.data(), X509_FILETYPE_PEM)) {
        raise_warning("error loading directory %s", item.data());
      } else {
        ndirs++;
      }
      dir_lookup = NULL;
    }
  }
  if (nfiles == 0) {
    file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
    if (file_lookup) {
      X509_LOOKUP_load_file(file_lookup, NULL, X509_FILETYPE_DEFAULT);
    }
  }
  if (ndirs == 0) {
    dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
    if (dir_lookup) {
      X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT);
    }
  }
  return store;
}

///////////////////////////////////////////////////////////////////////////////

static bool add_entries(X509_NAME *subj, const Array& items) {
  for (ArrayIter iter(items); iter; ++iter) {
    String index = iter.first();
    String item = iter.second();
    int nid = OBJ_txt2nid(index.data());
    if (nid != NID_undef) {
      if (!X509_NAME_add_entry_by_NID(subj, nid, MBSTRING_ASC,
                                      (unsigned char*)item.data(),
                                      -1, -1, 0)) {
        raise_warning("dn: add_entry_by_NID %d -> %s (failed)",
                        nid, item.data());
        return false;
      }
    } else {
      raise_warning("dn: %s is not a recognized name", index.data());
    }
  }
  return true;
}

static bool php_openssl_make_REQ(struct php_x509_request *req, X509_REQ *csr,
                                 const Array& dn, const Array& attribs) {
  char *dn_sect = CONF_get_string(req->req_config, req->section_name,
                                  "distinguished_name");
  if (dn_sect == NULL) return false;

  STACK_OF(CONF_VALUE) *dn_sk = CONF_get_section(req->req_config, dn_sect);
  if (dn_sk == NULL) return false;

  char *attr_sect = CONF_get_string(req->req_config, req->section_name,
                                    "attributes");
  STACK_OF(CONF_VALUE) *attr_sk = NULL;
  if (attr_sect) {
    attr_sk = CONF_get_section(req->req_config, attr_sect);
    if (attr_sk == NULL) {
      return false;
    }
  }

  /* setup the version number: version 1 */
  if (X509_REQ_set_version(csr, 0L)) {
    X509_NAME *subj = X509_REQ_get_subject_name(csr);
    if (!add_entries(subj, dn)) return false;

    /* Finally apply defaults from config file */
    for (int i = 0; i < sk_CONF_VALUE_num(dn_sk); i++) {
      CONF_VALUE *v = sk_CONF_VALUE_value(dn_sk, i);
      char *type = v->name;

      int len = strlen(type);
      if (len < (int)sizeof("_default")) {
        continue;
      }
      len -= sizeof("_default") - 1;
      if (strcmp("_default", type + len) != 0) {
        continue;
      }
      if (len > 200) {
        len = 200;
      }
      char buffer[200 + 1]; /*200 + \0 !*/
      memcpy(buffer, type, len);
      buffer[len] = '\0';
      type = buffer;

      /* Skip past any leading X. X: X, etc to allow for multiple instances */
      for (char *str = type; *str; str++) {
        if (*str == ':' || *str == ',' || *str == '.') {
          str++;
          if (*str) {
            type = str;
          }
          break;
        }
      }
      /* if it is already set, skip this */
      int nid = OBJ_txt2nid(type);
      if (X509_NAME_get_index_by_NID(subj, nid, -1) >= 0) {
        continue;
      }
      if (!X509_NAME_add_entry_by_txt(subj, type, MBSTRING_ASC,
                                      (unsigned char*)v->value, -1, -1, 0)) {
        raise_warning("add_entry_by_txt %s -> %s (failed)", type, v->value);
        return false;
      }
      if (!X509_NAME_entry_count(subj)) {
        raise_warning("no objects specified in config file");
        return false;
      }
    }

    if (!add_entries(subj, attribs)) return false;

    if (attr_sk) {
      for (int i = 0; i < sk_CONF_VALUE_num(attr_sk); i++) {
        CONF_VALUE *v = sk_CONF_VALUE_value(attr_sk, i);
        /* if it is already set, skip this */
        int nid = OBJ_txt2nid(v->name);
        if (X509_REQ_get_attr_by_NID(csr, nid, -1) >= 0) {
          continue;
        }
        if (!X509_REQ_add1_attr_by_txt(csr, v->name, MBSTRING_ASC,
                                       (unsigned char*)v->value, -1)) {
          /**
           * hzhao: mismatched version of conf file may have attributes that
           * are not recognizable, and I don't think it should be treated as
           * fatal errors.
           */
          Logger::Verbose("add1_attr_by_txt %s -> %s (failed)", v->name,
                          v->value);
          // return false;
        }
      }
    }
  }

  X509_REQ_set_pubkey(csr, req->priv_key);
  return true;
}

bool HHVM_FUNCTION(openssl_csr_export_to_file, const Variant& csr,
                                               const String& outfilename,
                                               bool notext /* = true */) {
  auto pcsr = CSRequest::Get(csr);
  if (!pcsr) return false;

  BIO *bio_out = BIO_new_file((char*)outfilename.data(), "w");
  if (bio_out == NULL) {
    raise_warning("error opening file %s", outfilename.data());
    return false;
  }

  if (!notext) {
    X509_REQ_print(bio_out, pcsr->csr());
  }
  PEM_write_bio_X509_REQ(bio_out, pcsr->csr());
  BIO_free(bio_out);
  return true;
}

bool HHVM_FUNCTION(openssl_csr_export, const Variant& csr, VRefParam out,
                                       bool notext /* = true */) {
  auto pcsr = CSRequest::Get(csr);
  if (!pcsr) return false;

  BIO *bio_out = BIO_new(BIO_s_mem());
  if (!notext) {
    X509_REQ_print(bio_out, pcsr->csr());
  }

  if (PEM_write_bio_X509_REQ(bio_out, pcsr->csr())) {
    BUF_MEM *bio_buf;
    BIO_get_mem_ptr(bio_out, &bio_buf);
    out.assignIfRef(String((char*)bio_buf->data, bio_buf->length, CopyString));
    BIO_free(bio_out);
    return true;
  }

  BIO_free(bio_out);
  return false;
}

Variant HHVM_FUNCTION(openssl_csr_get_public_key, const Variant& csr) {
  auto pcsr = CSRequest::Get(csr);
  if (!pcsr) return false;

  return Variant(req::make<Key>(X509_REQ_get_pubkey(pcsr->csr())));
}

Variant HHVM_FUNCTION(openssl_csr_get_subject, const Variant& csr,
                      bool use_shortnames /* = true */) {
  auto pcsr = CSRequest::Get(csr);
  if (!pcsr) return false;

  X509_NAME *subject = X509_REQ_get_subject_name(pcsr->csr());
  Array ret = Array::Create();
  add_assoc_name_entry(ret, NULL, subject, use_shortnames);
  return ret;
}

Variant HHVM_FUNCTION(openssl_csr_new,
                      const Variant& dn, VRefParam privkey,
                      const Variant& configargs /* = null_variant */,
                      const Variant& extraattribs /* = null_variant */) {
  Variant ret = false;
  struct php_x509_request req;
  memset(&req, 0, sizeof(req));

  req::ptr<Key> okey;
  X509_REQ *csr = NULL;
  std::vector<String> strings;
  if (php_openssl_parse_config(&req, configargs.toArray(), strings)) {
    /* Generate or use a private key */
    if (!privkey.isNull()) {
      okey = Key::Get(privkey, false);
      if (okey) {
        req.priv_key = okey->m_key;
      }
    }
    if (req.priv_key == NULL) {
      req.generatePrivateKey();
      if (req.priv_key) {
        okey = req::make<Key>(req.priv_key);
      }
    }
    if (req.priv_key == NULL) {
      raise_warning("Unable to generate a private key");
    } else {
      csr = X509_REQ_new();
      if (csr && php_openssl_make_REQ(&req, csr, dn.toArray(),
                                      extraattribs.toArray())) {
        X509V3_CTX ext_ctx;
        X509V3_set_ctx(&ext_ctx, NULL, NULL, csr, NULL, 0);
        X509V3_set_conf_lhash(&ext_ctx, req.req_config);

        /* Add extensions */
        if (req.request_extensions_section &&
            !X509V3_EXT_REQ_add_conf(req.req_config, &ext_ctx,
                                     (char*)req.request_extensions_section,
                                     csr)) {
          raise_warning("Error loading extension section %s",
                          req.request_extensions_section);
        } else {
          ret = true;
          if (X509_REQ_sign(csr, req.priv_key, req.digest)) {
            ret = req::make<CSRequest>(csr);
            csr = NULL;
          } else {
            raise_warning("Error signing request");
          }

          privkey.assignIfRef(Variant(okey));
        }
      }
    }
  }
  if (csr) {
    X509_REQ_free(csr);
  }
  php_openssl_dispose_config(&req);
  return ret;
}

Variant HHVM_FUNCTION(openssl_csr_sign, const Variant& csr,
                                        const Variant& cacert,
                                        const Variant& priv_key, int days,
                                        const Variant& configargs /* = null */,
                                        int serial /* = 0 */) {
  auto pcsr = CSRequest::Get(csr);
  if (!pcsr) return false;

  req::ptr<Certificate> ocert;
  if (!cacert.isNull()) {
    ocert = Certificate::Get(cacert);
    if (!ocert) {
      raise_warning("cannot get cert from parameter 2");
      return false;
    }
  }
  auto okey = Key::Get(priv_key, false);
  if (!okey) {
    raise_warning("cannot get private key from parameter 3");
    return false;
  }
  X509 *cert = NULL;
  if (ocert) {
    cert = ocert->m_cert;
  }
  EVP_PKEY *pkey = okey->m_key;
  if (cert && !X509_check_private_key(cert, pkey)) {
    raise_warning("private key does not correspond to signing cert");
    return false;
  }

  req::ptr<Certificate> onewcert;
  struct php_x509_request req;
  memset(&req, 0, sizeof(req));
  Variant ret = false;
  std::vector<String> strings;
  if (!php_openssl_parse_config(&req, configargs.toArray(), strings)) {
    goto cleanup;
  }

  /* Check that the request matches the signature */
  EVP_PKEY *key;
  key = X509_REQ_get_pubkey(pcsr->csr());
  if (key == NULL) {
    raise_warning("error unpacking public key");
    goto cleanup;
  }
  int i;
  i = X509_REQ_verify(pcsr->csr(), key);
  if (i < 0) {
    raise_warning("Signature verification problems");
    goto cleanup;
  }
  if (i == 0) {
    raise_warning("Signature did not match the certificate request");
    goto cleanup;
  }

  /* Now we can get on with it */
  X509 *new_cert;
  new_cert = X509_new();
  if (new_cert == NULL) {
    raise_warning("No memory");
    goto cleanup;
  }
  onewcert = req::make<Certificate>(new_cert);
  /* Version 3 cert */
  if (!X509_set_version(new_cert, 2)) {
    goto cleanup;
  }
  ASN1_INTEGER_set(X509_get_serialNumber(new_cert), serial);
  X509_set_subject_name(new_cert, X509_REQ_get_subject_name(pcsr->csr()));

  if (cert == NULL) {
    cert = new_cert;
  }
  if (!X509_set_issuer_name(new_cert, X509_get_subject_name(cert))) {
    goto cleanup;
  }
  X509_gmtime_adj(X509_get_notBefore(new_cert), 0);
  X509_gmtime_adj(X509_get_notAfter(new_cert), (long)60 * 60 * 24 * days);
  i = X509_set_pubkey(new_cert, key);
  if (!i) {
    goto cleanup;
  }
  if (req.extensions_section) {
    X509V3_CTX ctx;
    X509V3_set_ctx(&ctx, cert, new_cert, pcsr->csr(), nullptr, 0);
    X509V3_set_conf_lhash(&ctx, req.req_config);
    if (!X509V3_EXT_add_conf(req.req_config, &ctx, (char*)req.extensions_section,
                             new_cert)) {
      goto cleanup;
    }
  }

  /* Now sign it */
  if (!X509_sign(new_cert, pkey, req.digest)) {
    raise_warning("failed to sign it");
    goto cleanup;
  }

  /* Succeeded; lets return the cert */
  ret = onewcert;

 cleanup:
  php_openssl_dispose_config(&req);
  return ret;
}

Variant HHVM_FUNCTION(openssl_error_string) {
  char buf[512];
  unsigned long val = ERR_get_error();
  if (val) {
    return String(ERR_error_string(val, buf), CopyString);
  }
  return false;
}

bool HHVM_FUNCTION(openssl_open, const String& sealed_data, VRefParam open_data,
                                 const String& env_key,
                                 const Variant& priv_key_id,
                                 const String& method /* = null_string */) {
  const EVP_CIPHER *cipher_type;
  if (method.empty()) {
    cipher_type = EVP_rc4();
  } else {
    cipher_type = EVP_get_cipherbyname(method.c_str());
    if (!cipher_type) {
      raise_warning("Unknown cipher algorithm");
      return false;
    }
  }

  auto okey = Key::Get(priv_key_id, false);
  if (!okey) {
    raise_warning("unable to coerce parameter 4 into a private key");
    return false;
  }
  EVP_PKEY *pkey = okey->m_key;

  String s = String(sealed_data.size(), ReserveString);
  unsigned char *buf = (unsigned char *)s.mutableData();

  EVP_CIPHER_CTX ctx;
  int len1, len2;
  if (!EVP_OpenInit(&ctx, cipher_type, (unsigned char *)env_key.data(),
                    env_key.size(), NULL, pkey) ||

      EVP_CIPHER_CTX_iv_length(&ctx) > 0 ||
      !EVP_OpenUpdate(&ctx, buf, &len1, (unsigned char *)sealed_data.data(),
                      sealed_data.size()) ||
      !EVP_OpenFinal(&ctx, buf + len1, &len2) ||
      len1 + len2 == 0) {
    return false;
  }
  open_data.assignIfRef(s.setSize(len1 + len2));
  return true;
}

static STACK_OF(X509) *php_array_to_X509_sk(const Variant& certs) {
  STACK_OF(X509) *pcerts = sk_X509_new_null();
  Array arrCerts;
  if (certs.isArray()) {
    arrCerts = certs.toArray();
  } else {
    arrCerts.append(certs);
  }
  for (ArrayIter iter(arrCerts); iter; ++iter) {
    auto ocert = Certificate::Get(iter.second());
    if (!ocert) {
      break;
    }
    sk_X509_push(pcerts, ocert->m_cert);
  }
  return pcerts;
}

const StaticString
  s_friendly_name("friendly_name"),
  s_extracerts("extracerts");

static bool openssl_pkcs12_export_impl(const Variant& x509, BIO *bio_out,
                                       const Variant& priv_key, const String& pass,
                                       const Variant& args /* = null_variant */) {
  auto ocert = Certificate::Get(x509);
  if (!ocert) {
    raise_warning("cannot get cert from parameter 1");
    return false;
  }
  auto okey = Key::Get(priv_key, false);
  if (!okey) {
    raise_warning("cannot get private key from parameter 3");
    return false;
  }
  X509 *cert = ocert->m_cert;
  EVP_PKEY *key = okey->m_key;
  if (cert && !X509_check_private_key(cert, key)) {
    raise_warning("private key does not correspond to cert");
    return false;
  }

  Array arrArgs = args.toArray();

  String friendly_name;
  if (arrArgs.exists(s_friendly_name)) {
    friendly_name = arrArgs[s_friendly_name].toString();
  }

  STACK_OF(X509) *ca = NULL;
  if (arrArgs.exists(s_extracerts)) {
    ca = php_array_to_X509_sk(arrArgs[s_extracerts]);
  }

  PKCS12 *p12 = PKCS12_create
    ((char*)pass.data(),
     (char*)(friendly_name.empty() ? NULL : friendly_name.data()),
     key, cert, ca, 0, 0, 0, 0, 0);

  assert(bio_out);
  bool ret = i2d_PKCS12_bio(bio_out, p12);
  PKCS12_free(p12);
  sk_X509_free(ca);
  return ret;
}

bool HHVM_FUNCTION(openssl_pkcs12_export_to_file, const Variant& x509,
                                                  const String& filename,
                                                  const Variant& priv_key,
                                                  const String& pass,
                                    const Variant& args /* = null_variant */) {
  BIO *bio_out = BIO_new_file(filename.data(), "w");
  if (bio_out == NULL) {
    raise_warning("error opening file %s", filename.data());
    return false;
  }
  bool ret = openssl_pkcs12_export_impl(x509, bio_out, priv_key, pass, args);
  BIO_free(bio_out);
  return ret;
}

bool HHVM_FUNCTION(openssl_pkcs12_export, const Variant& x509, VRefParam out,
                                          const Variant& priv_key,
                                          const String& pass,
                                    const Variant& args /* = null_variant */) {
  BIO *bio_out = BIO_new(BIO_s_mem());
  bool ret = openssl_pkcs12_export_impl(x509, bio_out, priv_key, pass, args);
  if (ret) {
    BUF_MEM *bio_buf;
    BIO_get_mem_ptr(bio_out, &bio_buf);
    out.assignIfRef(String((char*)bio_buf->data, bio_buf->length, CopyString));
  }
  BIO_free(bio_out);
  return ret;
}

const StaticString
  s_cert("cert"),
  s_pkey("pkey");

bool HHVM_FUNCTION(openssl_pkcs12_read, const String& pkcs12, VRefParam certs,
                                        const String& pass) {
  bool ret = false;
  PKCS12 *p12 = NULL;

  BIO *bio_in = BIO_new(BIO_s_mem());
  if (!BIO_write(bio_in, pkcs12.data(), pkcs12.size())) {
    goto cleanup;
  }

  if (d2i_PKCS12_bio(bio_in, &p12)) {
    EVP_PKEY *pkey = NULL;
    X509 *cert = NULL;
    STACK_OF(X509) *ca = NULL;
    if (PKCS12_parse(p12, pass.data(), &pkey, &cert, &ca)) {
      Variant vcerts = Array::Create();
      SCOPE_EXIT {
        certs.assignIfRef(vcerts);
      };
      BIO *bio_out = BIO_new(BIO_s_mem());
      if (PEM_write_bio_X509(bio_out, cert)) {
        BUF_MEM *bio_buf;
        BIO_get_mem_ptr(bio_out, &bio_buf);
        vcerts.toArrRef().set(s_cert,
          String((char*)bio_buf->data, bio_buf->length, CopyString));
      }
      BIO_free(bio_out);

      bio_out = BIO_new(BIO_s_mem());
      if (PEM_write_bio_PrivateKey(bio_out, pkey, NULL, NULL, 0, 0, NULL)) {
        BUF_MEM *bio_buf;
        BIO_get_mem_ptr(bio_out, &bio_buf);
        vcerts.toArrRef().set(s_pkey,
          String((char*)bio_buf->data, bio_buf->length, CopyString));
      }
      BIO_free(bio_out);

      Array extracerts;
      for (X509 *aCA = sk_X509_pop(ca); aCA; aCA = sk_X509_pop(ca)) {
        bio_out = BIO_new(BIO_s_mem());
        if (PEM_write_bio_X509(bio_out, aCA)) {
          BUF_MEM *bio_buf;
          BIO_get_mem_ptr(bio_out, &bio_buf);
          extracerts.append(String((char*)bio_buf->data, bio_buf->length,
                                   CopyString));
        }
        BIO_free(bio_out);
        X509_free(aCA);
      }
      if (ca) {
        sk_X509_free(ca);
        vcerts.toArrRef().set(s_extracerts, extracerts);
      }
      ret = true;
      PKCS12_free(p12);
    }
  }

 cleanup:
  if (bio_in) {
    BIO_free(bio_in);
  }

  return ret;
}

bool HHVM_FUNCTION(openssl_pkcs7_decrypt, const String& infilename,
                                          const String& outfilename,
                                          const Variant& recipcert,
                                const Variant& recipkey /* = null_variant */) {
  bool ret = false;
  BIO *in = NULL, *out = NULL, *datain = NULL;
  PKCS7 *p7 = NULL;
  req::ptr<Key> okey;

  auto ocert = Certificate::Get(recipcert);
  if (!ocert) {
    raise_warning("unable to coerce parameter 3 to x509 cert");
    goto clean_exit;
  }

  okey = Key::Get(recipkey.isNull() ? recipcert : recipkey, false);
  if (!okey) {
    raise_warning("unable to get private key");
    goto clean_exit;
  }

  in = BIO_new_file(infilename.data(), "r");
  if (in == NULL) {
    raise_warning("error opening the file, %s", infilename.data());
    goto clean_exit;
  }
  out = BIO_new_file(outfilename.data(), "w");
  if (out == NULL) {
    raise_warning("error opening the file, %s", outfilename.data());
    goto clean_exit;
  }

  p7 = SMIME_read_PKCS7(in, &datain);
  if (p7 == NULL) {
    goto clean_exit;
  }
  assert(okey->m_key);
  assert(ocert->m_cert);
  if (PKCS7_decrypt(p7, okey->m_key, ocert->m_cert, out, PKCS7_DETACHED)) {
    ret = true;
  }

 clean_exit:
  PKCS7_free(p7);
  BIO_free(datain);
  BIO_free(in);
  BIO_free(out);

  return ret;
}

static void print_headers(BIO *outfile, const Array& headers) {
  if (!headers.isNull()) {
    if (headers->isVectorData()) {
      for (ArrayIter iter(headers); iter; ++iter) {
        BIO_printf(outfile, "%s\n", iter.second().toString().data());
      }
    } else {
      for (ArrayIter iter(headers); iter; ++iter) {
        BIO_printf(outfile, "%s: %s\n", iter.first().toString().data(),
                   iter.second().toString().data());
      }
    }
  }
}

bool HHVM_FUNCTION(openssl_pkcs7_encrypt, const String& infilename,
                                          const String& outfilename,
                                          const Variant& recipcerts,
                                          const Array& headers,
                                          int flags /* = 0 */,
                                int cipherid /* = k_OPENSSL_CIPHER_RC2_40 */) {
  bool ret = false;
  BIO *infile = NULL, *outfile = NULL;
  STACK_OF(X509) *precipcerts = NULL;
  PKCS7 *p7 = NULL;
  const EVP_CIPHER *cipher = NULL;

  infile = BIO_new_file(infilename.data(), "r");
  if (infile == NULL) {
    raise_warning("error opening the file, %s", infilename.data());
    goto clean_exit;
  }
  outfile = BIO_new_file(outfilename.data(), "w");
  if (outfile == NULL) {
    raise_warning("error opening the file, %s", outfilename.data());
    goto clean_exit;
  }

  precipcerts = php_array_to_X509_sk(recipcerts);

  /* sanity check the cipher */
  switch (cipherid) {
#ifndef OPENSSL_NO_RC2
  case PHP_OPENSSL_CIPHER_RC2_40:  cipher = EVP_rc2_40_cbc();   break;
  case PHP_OPENSSL_CIPHER_RC2_64:  cipher = EVP_rc2_64_cbc();   break;
  case PHP_OPENSSL_CIPHER_RC2_128: cipher = EVP_rc2_cbc();      break;
#endif
#ifndef OPENSSL_NO_DES
  case PHP_OPENSSL_CIPHER_DES:     cipher = EVP_des_cbc();      break;
  case PHP_OPENSSL_CIPHER_3DES:    cipher = EVP_des_ede3_cbc(); break;
#endif
  default:
    raise_warning("Invalid cipher type `%d'", cipherid);
    goto clean_exit;
  }
  if (cipher == NULL) {
    raise_warning("Failed to get cipher");
    goto clean_exit;
  }

  p7 = PKCS7_encrypt(precipcerts, infile, (EVP_CIPHER*)cipher, flags);
  if (p7 == NULL) goto clean_exit;

  print_headers(outfile, headers);
  (void)BIO_reset(infile);
  SMIME_write_PKCS7(outfile, p7, infile, flags);
  ret = true;

 clean_exit:
  PKCS7_free(p7);
  BIO_free(infile);
  BIO_free(outfile);
  sk_X509_free(precipcerts);
  return ret;
}

bool HHVM_FUNCTION(openssl_pkcs7_sign, const String& infilename,
                                       const String& outfilename,
                                       const Variant& signcert,
                                       const Variant& privkey,
                                       const Variant& headers,
                                       int flags /* = k_PKCS7_DETACHED */,
                                const String& extracerts /* = null_string */) {
  bool ret = false;
  STACK_OF(X509) *others = NULL;
  BIO *infile = NULL, *outfile = NULL;
  PKCS7 *p7 = NULL;
  req::ptr<Key> okey;
  req::ptr<Certificate> ocert;

  if (!extracerts.empty()) {
    others = load_all_certs_from_file(extracerts.data());
    if (others == NULL) {
      goto clean_exit;
    }
  }

  okey = Key::Get(privkey, false);
  if (!okey) {
    raise_warning("error getting private key");
    goto clean_exit;
  }
  EVP_PKEY *key;
  key = okey->m_key;

  ocert = Certificate::Get(signcert);
  if (!ocert) {
    raise_warning("error getting cert");
    goto clean_exit;
  }
  X509 *cert;
  cert = ocert->m_cert;

  infile = BIO_new_file(infilename.data(), "r");
  if (infile == NULL) {
    raise_warning("error opening input file %s!", infilename.data());
    goto clean_exit;
  }

  outfile = BIO_new_file(outfilename.data(), "w");
  if (outfile == NULL) {
    raise_warning("error opening output file %s!", outfilename.data());
    goto clean_exit;
  }

  p7 = PKCS7_sign(cert, key, others, infile, flags);
  if (p7 == NULL) {
    raise_warning("error creating PKCS7 structure!");
    goto clean_exit;
  }

  print_headers(outfile, headers.toArray());
  (void)BIO_reset(infile);
  SMIME_write_PKCS7(outfile, p7, infile, flags);
  ret = true;

 clean_exit:
  PKCS7_free(p7);
  BIO_free(infile);
  BIO_free(outfile);
  if (others) {
    sk_X509_pop_free(others, X509_free);
  }

  return ret;
}

static int pkcs7_ignore_expiration(int ok, X509_STORE_CTX *ctx) {
  if (ok) {
    return ok;
  }
  int error = X509_STORE_CTX_get_error(ctx);
  if (error == X509_V_ERR_CERT_HAS_EXPIRED) {
    // ignore cert expirations
    Logger::Verbose("Ignoring cert expiration");
    return 1;
  }
  return ok;
}

/**
 *  NOTE: when ignore_cert_expiration is true, a custom certificate validation
 *  callback is set up. Please be aware of this if you modify the function to
 *  allow other certificate validation behaviors
 */
Variant openssl_pkcs7_verify_core(
  const String& filename,
  int flags,
  const Variant& voutfilename /* = null_string */,
  const Variant& vcainfo /* = null_array */,
  const Variant& vextracerts /* = null_string */,
  const Variant& vcontent /* = null_string */,
  bool ignore_cert_expiration
) {
  Variant ret = -1;
  X509_STORE *store = NULL;
  BIO *in = NULL;
  PKCS7 *p7 = NULL;
  BIO *datain = NULL;
  BIO *dataout = NULL;

  auto cainfo = vcainfo.toArray();
  auto extracerts = vextracerts.toString();
  auto content = vcontent.toString();

  STACK_OF(X509) *others = NULL;
  if (!extracerts.empty()) {
    others = load_all_certs_from_file(extracerts.data());
    if (others == NULL) {
      goto clean_exit;
    }
  }

  flags = flags & ~PKCS7_DETACHED;

  store = setup_verify(cainfo);
  if (!store) {
    goto clean_exit;
  }
  if (ignore_cert_expiration) {
#if (OPENSSL_VERSION_NUMBER >= 0x10000000)
    // make sure no other callback is specified
    assert(!store->verify_cb);
    // ignore expired certs
    X509_STORE_set_verify_cb(store, pkcs7_ignore_expiration);
#else
    always_assert(false);
#endif
  }
  in = BIO_new_file(filename.data(), (flags & PKCS7_BINARY) ? "rb" : "r");
  if (in == NULL) {
    raise_warning("error opening the file, %s", filename.data());
    goto clean_exit;
  }

  p7 = SMIME_read_PKCS7(in, &datain);
  if (p7 == NULL) {
    goto clean_exit;
  }

  if (!content.empty()) {
    dataout = BIO_new_file(content.data(), "w");
    if (dataout == NULL) {
      raise_warning("error opening the file, %s", content.data());
      goto clean_exit;
    }
  }

  if (PKCS7_verify(p7, others, store, datain, dataout, flags)) {
    ret = true;
    auto outfilename = voutfilename.toString();
    if (!outfilename.empty()) {
      BIO *certout = BIO_new_file(outfilename.data(), "w");
      if (certout) {
        STACK_OF(X509) *signers = PKCS7_get0_signers(p7, NULL, flags);
        for (int i = 0; i < sk_X509_num(signers); i++) {
          PEM_write_bio_X509(certout, sk_X509_value(signers, i));
        }
        BIO_free(certout);
        sk_X509_free(signers);
      } else {
        raise_warning("signature OK, but cannot open %s for writing",
                        outfilename.data());
        ret = -1;
      }
    }
    goto clean_exit;
  } else {
    ret = false;
  }

 clean_exit:
  X509_STORE_free(store);
  BIO_free(datain);
  BIO_free(in);
  BIO_free(dataout);
  PKCS7_free(p7);
  sk_X509_free(others);

  return ret;
}

Variant HHVM_FUNCTION(openssl_pkcs7_verify, const String& filename, int flags,
                               const Variant& voutfilename /* = null_string */,
                               const Variant& vcainfo /* = null_array */,
                               const Variant& vextracerts /* = null_string */,
                               const Variant& vcontent /* = null_string */) {
  return openssl_pkcs7_verify_core(filename, flags, voutfilename, vcainfo,
                                   vextracerts, vcontent, false);
}

static bool openssl_pkey_export_impl(const Variant& key, BIO *bio_out,
                                     const String& passphrase /* = null_string */,
                                     const Variant& configargs /* = null_variant */) {
  auto okey = Key::Get(key, false, passphrase.data());
  if (!okey) {
    raise_warning("cannot get key from parameter 1");
    return false;
  }
  EVP_PKEY *pkey = okey->m_key;

  struct php_x509_request req;
  memset(&req, 0, sizeof(req));
  std::vector<String> strings;
  bool ret = false;
  if (php_openssl_parse_config(&req, configargs.toArray(), strings)) {
    const EVP_CIPHER *cipher;
    if (!passphrase.empty() && req.priv_key_encrypt) {
      cipher = (EVP_CIPHER *)EVP_des_ede3_cbc();
    } else {
      cipher = NULL;
    }
    assert(bio_out);
    ret = PEM_write_bio_PrivateKey(bio_out, pkey, cipher,
                                   (unsigned char *)passphrase.data(),
                                   passphrase.size(), NULL, NULL);
  }
  php_openssl_dispose_config(&req);
  return ret;
}

bool HHVM_FUNCTION(openssl_pkey_export_to_file, const Variant& key,
                                                const String& outfilename,
                                   const String& passphrase /* = null_string */,
                               const Variant& configargs /* = null_variant */) {
  BIO *bio_out = BIO_new_file(outfilename.data(), "w");
  if (bio_out == NULL) {
    raise_warning("error opening the file, %s", outfilename.data());
    return false;
  }
  bool ret = openssl_pkey_export_impl(key, bio_out, passphrase, configargs);
  BIO_free(bio_out);
  return ret;
}

bool HHVM_FUNCTION(openssl_pkey_export, const Variant& key, VRefParam out,
                                   const String& passphrase /* = null_string */,
                              const Variant& configargs /* = null_variant */) {
  BIO *bio_out = BIO_new(BIO_s_mem());
  bool ret = openssl_pkey_export_impl(key, bio_out, passphrase, configargs);
  if (ret) {
    char *bio_mem_ptr;
    long bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr);
    out.assignIfRef(String(bio_mem_ptr, bio_mem_len, CopyString));
  }
  BIO_free(bio_out);
  return ret;
}

const StaticString
  s_bits("bits"),
  s_key("key"),
  s_type("type"),
  s_name("name"),
  s_hash("hash"),
  s_version("version"),
  s_serialNumber("serialNumber"),
  s_validFrom("validFrom"),
  s_validTo("validTo"),
  s_validFrom_time_t("validFrom_time_t"),
  s_validTo_time_t("validTo_time_t"),
  s_alias("alias"),
  s_purposes("purposes"),
  s_extensions("extensions"),
  s_rsa("rsa"),
  s_dsa("dsa"),
  s_dh("dh"),
  s_n("n"),
  s_e("e"),
  s_d("d"),
  s_p("p"),
  s_q("q"),
  s_g("g"),
  s_dmp1("dmp1"),
  s_dmq1("dmq1"),
  s_iqmp("iqmp"),
  s_priv_key("priv_key"),
  s_pub_key("pub_key");

static void add_bignum_as_string(Array &arr,
                                 StaticString key,
                                 BIGNUM *bn) {
  if (!bn) {
    return;
  }
  int num_bytes = BN_num_bytes(bn);
  unsigned char *out = (unsigned char *)req::malloc(num_bytes);
  BN_bn2bin(bn, out);
  arr.set(key, String((const char *)out, num_bytes, CopyString));
  req::free(out);
}

Array HHVM_FUNCTION(openssl_pkey_get_details, const Resource& key) {
  EVP_PKEY *pkey = cast<Key>(key)->m_key;
  BIO *out = BIO_new(BIO_s_mem());
  PEM_write_bio_PUBKEY(out, pkey);
  char *pbio;
  unsigned int pbio_len = BIO_get_mem_data(out, &pbio);

  Array ret;
  ret.set(s_bits, EVP_PKEY_bits(pkey));
  ret.set(s_key, String(pbio, pbio_len, CopyString));
  long ktype = -1;

  Array details;
  RSA *rsa = pkey->pkey.rsa;
  DSA *dsa = pkey->pkey.dsa;
  DH *dh = pkey->pkey.dh;
  switch (EVP_PKEY_type(pkey->type)) {
  case EVP_PKEY_RSA:
  case EVP_PKEY_RSA2:
    ktype = OPENSSL_KEYTYPE_RSA;
    assert(rsa);
    add_bignum_as_string(details, s_n, rsa->n);
    add_bignum_as_string(details, s_e, rsa->e);
    add_bignum_as_string(details, s_d, rsa->d);
    add_bignum_as_string(details, s_p, rsa->p);
    add_bignum_as_string(details, s_q, rsa->q);
    add_bignum_as_string(details, s_dmp1, rsa->dmp1);
    add_bignum_as_string(details, s_dmq1, rsa->dmq1);
    add_bignum_as_string(details, s_iqmp, rsa->iqmp);
    ret.set(s_rsa, details);
    break;
  case EVP_PKEY_DSA:
  case EVP_PKEY_DSA2:
  case EVP_PKEY_DSA3:
  case EVP_PKEY_DSA4:
    ktype = OPENSSL_KEYTYPE_DSA;
    assert(dsa);
    add_bignum_as_string(details, s_p, dsa->p);
    add_bignum_as_string(details, s_q, dsa->q);
    add_bignum_as_string(details, s_g, dsa->g);
    add_bignum_as_string(details, s_priv_key, dsa->priv_key);
    add_bignum_as_string(details, s_pub_key, dsa->pub_key);
    ret.set(s_dsa, details);
    break;
  case EVP_PKEY_DH:
    ktype = OPENSSL_KEYTYPE_DH;
    assert(dh);
    add_bignum_as_string(details, s_p, dh->p);
    add_bignum_as_string(details, s_g, dh->g);
    add_bignum_as_string(details, s_priv_key, dh->priv_key);
    add_bignum_as_string(details, s_pub_key, dh->pub_key);
    ret.set(s_dh, details);
    break;
#ifdef EVP_PKEY_EC
  case EVP_PKEY_EC:      ktype = OPENSSL_KEYTYPE_EC;    break;
#endif
  }
  ret.set(s_type, ktype);
  BIO_free(out);
  return ret;
}

Variant HHVM_FUNCTION(openssl_pkey_get_private, const Variant& key,
                                 const String& passphrase /* = null_string */) {
  return toVariant(Key::Get(key, false, passphrase.data()));
}

Variant HHVM_FUNCTION(openssl_pkey_get_public, const Variant& certificate) {
  return toVariant(Key::Get(certificate, true));
}

Resource HHVM_FUNCTION(openssl_pkey_new,
                       const Variant& configargs /* = null_variant */) {
  struct php_x509_request req;
  memset(&req, 0, sizeof(req));

  Resource ret;
  std::vector<String> strings;
  if (php_openssl_parse_config(&req, configargs.toArray(), strings) &&
      req.generatePrivateKey()) {
    ret = Resource(req::make<Key>(req.priv_key));
  }

  php_openssl_dispose_config(&req);
  return ret;
}

bool HHVM_FUNCTION(openssl_private_decrypt, const String& data,
                                            VRefParam decrypted,
                                            const Variant& key,
                                  int padding /* = k_OPENSSL_PKCS1_PADDING */) {
  auto okey = Key::Get(key, false);
  if (!okey) {
    raise_warning("key parameter is not a valid private key");
    return false;
  }
  EVP_PKEY *pkey = okey->m_key;
  int cryptedlen = EVP_PKEY_size(pkey);
  String s = String(cryptedlen, ReserveString);
  unsigned char *cryptedbuf = (unsigned char *)s.mutableData();

  int successful = 0;
  switch (pkey->type) {
  case EVP_PKEY_RSA:
  case EVP_PKEY_RSA2:
    cryptedlen = RSA_private_decrypt(data.size(),
                                     (unsigned char *)data.data(),
                                     cryptedbuf,
                                     pkey->pkey.rsa,
                                     padding);
    if (cryptedlen != -1) {
      successful = 1;
    }
    break;

  default:
    raise_warning("key type not supported");
  }

  if (successful) {
    decrypted.assignIfRef(s.setSize(cryptedlen));
    return true;
  }

  return false;
}

bool HHVM_FUNCTION(openssl_private_encrypt, const String& data,
                                            VRefParam crypted,
                                            const Variant& key,
                                  int padding /* = k_OPENSSL_PKCS1_PADDING */) {
  auto okey = Key::Get(key, false);
  if (!okey) {
    raise_warning("key param is not a valid private key");
    return false;
  }
  EVP_PKEY *pkey = okey->m_key;
  int cryptedlen = EVP_PKEY_size(pkey);
  String s = String(cryptedlen, ReserveString);
  unsigned char *cryptedbuf = (unsigned char *)s.mutableData();

  int successful = 0;
  switch (pkey->type) {
  case EVP_PKEY_RSA:
  case EVP_PKEY_RSA2:
    successful = (RSA_private_encrypt(data.size(),
                                      (unsigned char *)data.data(),
                                      cryptedbuf,
                                      pkey->pkey.rsa,
                                      padding) == cryptedlen);
    break;
  default:
    raise_warning("key type not supported");
  }

  if (successful) {
    crypted.assignIfRef(s.setSize(cryptedlen));
    return true;
  }

  return false;
}

bool HHVM_FUNCTION(openssl_public_decrypt, const String& data,
                                           VRefParam decrypted,
                                           const Variant& key,
                                  int padding /* = k_OPENSSL_PKCS1_PADDING */) {
  auto okey = Key::Get(key, true);
  if (!okey) {
    raise_warning("key parameter is not a valid public key");
    return false;
  }
  EVP_PKEY *pkey = okey->m_key;
  int cryptedlen = EVP_PKEY_size(pkey);
  String s = String(cryptedlen, ReserveString);
  unsigned char *cryptedbuf = (unsigned char *)s.mutableData();

  int successful = 0;
  switch (pkey->type) {
  case EVP_PKEY_RSA:
  case EVP_PKEY_RSA2:
    cryptedlen = RSA_public_decrypt(data.size(),
                                    (unsigned char *)data.data(),
                                    cryptedbuf,
                                    pkey->pkey.rsa,
                                    padding);
    if (cryptedlen != -1) {
      successful = 1;
    }
    break;

  default:
    raise_warning("key type not supported");
  }

  if (successful) {
    decrypted.assignIfRef(s.setSize(cryptedlen));
    return true;
  }

  return false;
}

bool HHVM_FUNCTION(openssl_public_encrypt, const String& data,
                                           VRefParam crypted,
                                           const Variant& key,
                                  int padding /* = k_OPENSSL_PKCS1_PADDING */) {
  auto okey = Key::Get(key, true);
  if (!okey) {
    raise_warning("key parameter is not a valid public key");
    return false;
  }
  EVP_PKEY *pkey = okey->m_key;
  int cryptedlen = EVP_PKEY_size(pkey);
  String s = String(cryptedlen, ReserveString);
  unsigned char *cryptedbuf = (unsigned char *)s.mutableData();

  int successful = 0;
  switch (pkey->type) {
  case EVP_PKEY_RSA:
  case EVP_PKEY_RSA2:
    successful = (RSA_public_encrypt(data.size(),
                                     (unsigned char *)data.data(),
                                     cryptedbuf,
                                     pkey->pkey.rsa,
                                     padding) == cryptedlen);
    break;
  default:
    raise_warning("key type not supported");
  }

  if (successful) {
    crypted.assignIfRef(s.setSize(cryptedlen));
    return true;
  }

  return false;
}

Variant HHVM_FUNCTION(openssl_seal, const String& data, VRefParam sealed_data,
                                    VRefParam env_keys,
                                    const Array& pub_key_ids,
                                    const String& method /* = null_string */) {
  int nkeys = pub_key_ids.size();
  if (nkeys == 0) {
    raise_warning("Fourth argument to openssl_seal() must be "
                    "a non-empty array");
    return false;
  }

  const EVP_CIPHER *cipher_type;
  if (method.empty()) {
    cipher_type = EVP_rc4();
  } else {
    cipher_type = EVP_get_cipherbyname(method.c_str());
    if (!cipher_type) {
      raise_warning("Unknown cipher algorithm");
      return false;
    }
  }

  EVP_PKEY **pkeys = (EVP_PKEY**)malloc(nkeys * sizeof(*pkeys));
  int *eksl = (int*)malloc(nkeys * sizeof(*eksl));
  unsigned char **eks = (unsigned char **)malloc(nkeys * sizeof(*eks));
  memset(eks, 0, sizeof(*eks) * nkeys);

  // holder is needed to make sure none of the Keys get deleted prematurely.
  // The pkeys array points to elements inside of Keys returned from Key::Get()
  // which may be newly allocated and have no other owners.
  std::vector<req::ptr<Key>> holder;

  /* get the public keys we are using to seal this data */
  bool ret = true;
  int i = 0;
  String s;
  unsigned char *buf = NULL;
  for (ArrayIter iter(pub_key_ids); iter; ++iter, ++i) {
    auto okey = Key::Get(iter.second(), true);
    if (!okey) {
      raise_warning("not a public key (%dth member of pubkeys)", i + 1);
      ret = false;
      goto clean_exit;
    }
    holder.push_back(okey);
    pkeys[i] = okey->m_key;
    eks[i] = (unsigned char *)malloc(EVP_PKEY_size(pkeys[i]) + 1);
  }

  EVP_CIPHER_CTX ctx;
  if (!EVP_EncryptInit(&ctx, cipher_type, nullptr, nullptr)) {
    ret = false;
    goto clean_exit;
  }

  if (EVP_CIPHER_CTX_iv_length(&ctx) > 0) {
    raise_warning("Cipher algorithm requires an IV");
    ret = false;
    goto clean_exit;
  }

  int len1, len2;

  s = String(data.size() + EVP_CIPHER_CTX_block_size(&ctx), ReserveString);
  buf = (unsigned char *)s.mutableData();
  if (!EVP_SealInit(&ctx, cipher_type, eks, eksl, nullptr, pkeys, nkeys) ||
      !EVP_SealUpdate(&ctx, buf, &len1, (unsigned char *)data.data(),
                      data.size())) {
    ret = false;
    goto clean_exit;
  }

  EVP_SealFinal(&ctx, buf + len1, &len2);
  if (len1 + len2 > 0) {
    sealed_data.assignIfRef(s.setSize(len1 + len2));

    Array ekeys;
    for (int i = 0; i < nkeys; i++) {
      eks[i][eksl[i]] = '\0';
      ekeys.append(String((char*)eks[i], eksl[i], AttachString));
      eks[i] = NULL;
    }
    env_keys.assignIfRef(ekeys);
  }

 clean_exit:
  for (int i = 0; i < nkeys; i++) {
    if (eks[i]) free(eks[i]);
  }
  free(eks);
  free(eksl);
  free(pkeys);

  if (ret) return len1 + len2;
  return false;
}

static const EVP_MD *php_openssl_get_evp_md_from_algo(long algo) {
  switch (algo) {
  case OPENSSL_ALGO_SHA1: return EVP_sha1();
  case OPENSSL_ALGO_MD5:  return EVP_md5();
  case OPENSSL_ALGO_MD4:  return EVP_md4();
#ifdef HAVE_OPENSSL_MD2_H
  case OPENSSL_ALGO_MD2:  return EVP_md2();
#endif
  case OPENSSL_ALGO_DSS1: return EVP_dss1();
#if OPENSSL_VERSION_NUMBER >= 0x0090708fL
  case OPENSSL_ALGO_SHA224: return EVP_sha224();
  case OPENSSL_ALGO_SHA256: return EVP_sha256();
  case OPENSSL_ALGO_SHA384: return EVP_sha384();
  case OPENSSL_ALGO_SHA512: return EVP_sha512();
  case OPENSSL_ALGO_RMD160: return EVP_ripemd160();
#endif
  }
  return NULL;
}

bool HHVM_FUNCTION(openssl_sign, const String& data, VRefParam signature,
                                 const Variant& priv_key_id,
                     const Variant& signature_alg /* = k_OPENSSL_ALGO_SHA1 */) {
  auto okey = Key::Get(priv_key_id, false);
  if (!okey) {
    raise_warning("supplied key param cannot be coerced into a private key");
    return false;
  }

  const EVP_MD *mdtype = nullptr;
  if (signature_alg.isInteger()) {
    mdtype = php_openssl_get_evp_md_from_algo(signature_alg.toInt64Val());
  } else if (signature_alg.isString()) {
    mdtype = EVP_get_digestbyname(signature_alg.toString().data());
  }

  if (!mdtype) {
    raise_warning("Unknown signature algorithm.");
    return false;
  }

  EVP_PKEY *pkey = okey->m_key;
  int siglen = EVP_PKEY_size(pkey);
  String s = String(siglen, ReserveString);
  unsigned char *sigbuf = (unsigned char *)s.mutableData();

  EVP_MD_CTX md_ctx;
  EVP_SignInit(&md_ctx, mdtype);
  EVP_SignUpdate(&md_ctx, (unsigned char *)data.data(), data.size());
  if (EVP_SignFinal(&md_ctx, sigbuf, (unsigned int *)&siglen, pkey)) {
    signature.assignIfRef(s.setSize(siglen));
#if OPENSSL_VERSION_NUMBER >= 0x0090700fL
    EVP_MD_CTX_cleanup(&md_ctx);
#endif
    return true;
  }
#if OPENSSL_VERSION_NUMBER >= 0x0090700fL
  EVP_MD_CTX_cleanup(&md_ctx);
#endif
  return false;
}

Variant HHVM_FUNCTION(openssl_verify, const String& data,
                                      const String& signature,
                                      const Variant& pub_key_id,
                     const Variant& signature_alg /* = k_OPENSSL_ALGO_SHA1 */) {
  int err;
  const EVP_MD *mdtype = nullptr;

  if (signature_alg.isInteger()) {
    mdtype = php_openssl_get_evp_md_from_algo(signature_alg.toInt64Val());
  } else if (signature_alg.isString()) {
    mdtype = EVP_get_digestbyname(signature_alg.toString().data());
  }

  if (!mdtype) {
    raise_warning("Unknown signature algorithm.");
    return false;
  }

  auto okey = Key::Get(pub_key_id, true);
  if (!okey) {
    raise_warning("supplied key param cannot be coerced into a public key");
    return false;
  }

  EVP_MD_CTX md_ctx;
  EVP_VerifyInit(&md_ctx, mdtype);
  EVP_VerifyUpdate(&md_ctx, (unsigned char*)data.data(), data.size());
  err = EVP_VerifyFinal(&md_ctx, (unsigned char *)signature.data(),
                         signature.size(), okey->m_key);
#if OPENSSL_VERSION_NUMBER >= 0x0090700fL
  EVP_MD_CTX_cleanup(&md_ctx);
#endif
  return err;
}

bool HHVM_FUNCTION(openssl_x509_check_private_key, const Variant& cert,
                                                   const Variant& key) {
  auto ocert = Certificate::Get(cert);
  if (!ocert) {
    return false;
  }
  auto okey = Key::Get(key, false);
  if (!okey) {
    return false;
  }
  return X509_check_private_key(ocert->m_cert, okey->m_key);
}

static int check_cert(X509_STORE *ctx, X509 *x, STACK_OF(X509) *untrustedchain,
                      int purpose) {
  X509_STORE_CTX *csc = X509_STORE_CTX_new();
  if (csc == NULL) {
    raise_warning("memory allocation failure");
    return 0;
  }
  X509_STORE_CTX_init(csc, ctx, x, untrustedchain);

  if (purpose >= 0) {
    X509_STORE_CTX_set_purpose(csc, purpose);
  }

  int ret = X509_verify_cert(csc);
  X509_STORE_CTX_free(csc);
  return ret;
}

Variant HHVM_FUNCTION(openssl_x509_checkpurpose, const Variant& x509cert,
                      int purpose,
                      const Array& cainfo /* = null_array */,
                      const String& untrustedfile /* = null_string */) {
  int ret = -1;
  STACK_OF(X509) *untrustedchain = NULL;
  X509_STORE *pcainfo = NULL;
  req::ptr<Certificate> ocert;

  if (!untrustedfile.empty()) {
    untrustedchain = load_all_certs_from_file(untrustedfile.data());
    if (untrustedchain == NULL) {
      goto clean_exit;
    }
  }

  pcainfo = setup_verify(cainfo);
  if (pcainfo == NULL) {
    goto clean_exit;
  }

  ocert = Certificate::Get(x509cert);
  if (!ocert) {
    raise_warning("cannot get cert from parameter 1");
    return false;
  }
  X509 *cert;
  cert = ocert->m_cert;
  assert(cert);

  ret = check_cert(pcainfo, cert, untrustedchain, purpose);

 clean_exit:
  if (pcainfo) {
    X509_STORE_free(pcainfo);
  }
  if (untrustedchain) {
    sk_X509_pop_free(untrustedchain, X509_free);
  }
  return ret == 1 ? true : ret == 0 ? false : -1;
}

static bool openssl_x509_export_impl(const Variant& x509, BIO *bio_out,
                                     bool notext /* = true */) {
  auto ocert = Certificate::Get(x509);
  if (!ocert) {
    raise_warning("cannot get cert from parameter 1");
    return false;
  }
  X509 *cert = ocert->m_cert;
  assert(cert);

  assert(bio_out);
  if (!notext) {
    X509_print(bio_out, cert);
  }
  return PEM_write_bio_X509(bio_out, cert);
}

bool HHVM_FUNCTION(openssl_x509_export_to_file, const Variant& x509,
                                                const String& outfilename,
                                                bool notext /* = true */) {
  BIO *bio_out = BIO_new_file((char*)outfilename.data(), "w");
  if (bio_out == NULL) {
    raise_warning("error opening file %s", outfilename.data());
    return false;
  }
  bool ret = openssl_x509_export_impl(x509, bio_out, notext);
  BIO_free(bio_out);
  return ret;
}

bool HHVM_FUNCTION(openssl_x509_export, const Variant& x509, VRefParam output,
                                        bool notext /* = true */) {
  BIO *bio_out = BIO_new(BIO_s_mem());
  bool ret = openssl_x509_export_impl(x509, bio_out, notext);
  if (ret) {
    BUF_MEM *bio_buf;
    BIO_get_mem_ptr(bio_out, &bio_buf);
    output.assignIfRef(String(bio_buf->data, bio_buf->length, CopyString));
  }
  BIO_free(bio_out);
  return ret;
}

/**
 * This is how the time string is formatted:
 *
 * snprintf(p, sizeof(p), "%02d%02d%02d%02d%02d%02dZ",ts->tm_year%100,
 *          ts->tm_mon+1,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec);
 */
static time_t asn1_time_to_time_t(ASN1_UTCTIME *timestr) {

  if (ASN1_STRING_type(timestr) != V_ASN1_UTCTIME) {
    raise_warning("illegal ASN1 data type for timestamp");
    return (time_t)-1;
  }

  // Binary safety
  if (ASN1_STRING_length(timestr) != strlen((char*)ASN1_STRING_data(timestr))) {
    raise_warning("illegal length in timestamp");
    return (time_t)-1;
  }

  if (ASN1_STRING_length(timestr) < 13) {
    raise_warning("unable to parse time string %s correctly",
                    timestr->data);
    return (time_t)-1;
  }

  char *strbuf = strdup((char*)timestr->data);

  struct tm thetime;
  memset(&thetime, 0, sizeof(thetime));

  /* we work backwards so that we can use atoi more easily */
  char *thestr = strbuf + ASN1_STRING_length(timestr) - 3;
  thetime.tm_sec  = atoi(thestr);   *thestr = '\0';  thestr -= 2;
  thetime.tm_min  = atoi(thestr);   *thestr = '\0';  thestr -= 2;
  thetime.tm_hour = atoi(thestr);   *thestr = '\0';  thestr -= 2;
  thetime.tm_mday = atoi(thestr);   *thestr = '\0';  thestr -= 2;
  thetime.tm_mon  = atoi(thestr)-1; *thestr = '\0';  thestr -= 2;
  thetime.tm_year = atoi(thestr);
  if (thetime.tm_year < 68) {
    thetime.tm_year += 100;
  }

  thetime.tm_isdst = -1;
  time_t ret = mktime(&thetime);

  long gmadjust = 0;
#if HAVE_TM_GMTOFF
  gmadjust = thetime.tm_gmtoff;
#elif defined(_MSC_VER)
  TIME_ZONE_INFORMATION inf;
  GetTimeZoneInformation(&inf);
  gmadjust = thetime.tm_isdst ? inf.DaylightBias : inf.StandardBias;
#else
  /**
   * If correcting for daylight savings time, we set the adjustment to
   * the value of timezone - 3600 seconds. Otherwise, we need to overcorrect
   * and set the adjustment to the main timezone + 3600 seconds.
   */
  gmadjust = -(thetime.tm_isdst ?
               (long)timezone - 3600 : (long)timezone + 3600);
#endif
  ret += gmadjust;
  free(strbuf);
  return ret;
}

/* Special handling of subjectAltName, see CVE-2013-4073
 * Christian Heimes
 */

static int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension)
{
  GENERAL_NAMES *names;
  const X509V3_EXT_METHOD *method = nullptr;
  long i, length, num;
  const unsigned char *p;

  method = X509V3_EXT_get(extension);
  if (method == nullptr) {
    return -1;
  }

  p = extension->value->data;
  length = extension->value->length;
  if (method->it) {
    names = (GENERAL_NAMES*)(ASN1_item_d2i(nullptr, &p, length,
                                          ASN1_ITEM_ptr(method->it)));
  } else {
    names = (GENERAL_NAMES*)(method->d2i(nullptr, &p, length));
  }
  if (names == nullptr) {
    return -1;
  }

  num = sk_GENERAL_NAME_num(names);
  for (i = 0; i < num; i++) {
    GENERAL_NAME *name;
    ASN1_STRING *as;
    name = sk_GENERAL_NAME_value(names, i);
    switch (name->type) {
      case GEN_EMAIL:
             BIO_puts(bio, "email:");
             as = name->d.rfc822Name;
             BIO_write(bio, ASN1_STRING_data(as),
                       ASN1_STRING_length(as));
             break;
      case GEN_DNS:
             BIO_puts(bio, "DNS:");
             as = name->d.dNSName;
             BIO_write(bio, ASN1_STRING_data(as),
                       ASN1_STRING_length(as));
             break;
      case GEN_URI:
             BIO_puts(bio, "URI:");
             as = name->d.uniformResourceIdentifier;
             BIO_write(bio, ASN1_STRING_data(as),
                       ASN1_STRING_length(as));
             break;
      default:
             /* use builtin print for GEN_OTHERNAME, GEN_X400,
              * GEN_EDIPARTY, GEN_DIRNAME, GEN_IPADD and GEN_RID
              */
             GENERAL_NAME_print(bio, name);
    }
    /* trailing ', ' except for last element */
    if (i < (num - 1)) {
           BIO_puts(bio, ", ");
    }
  }
  sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);

  return 0;
}
Variant HHVM_FUNCTION(openssl_x509_parse, const Variant& x509cert,
                                          bool shortnames /* = true */) {
  auto ocert = Certificate::Get(x509cert);
  if (!ocert) {
    return false;
  }
  X509 *cert = ocert->m_cert;
  assert(cert);

  Array ret;
  if (cert->name) {
    ret.set(s_name, String(cert->name, CopyString));
  }
  add_assoc_name_entry(ret, "subject", X509_get_subject_name(cert),
                       shortnames);
  /* hash as used in CA directories to lookup cert by subject name */
  {
    char buf[32];
    snprintf(buf, sizeof(buf), "%08lx", X509_subject_name_hash(cert));
    ret.set(s_hash, String(buf, CopyString));
  }

  add_assoc_name_entry(ret, "issuer", X509_get_issuer_name(cert), shortnames);
  ret.set(s_version, X509_get_version(cert));

  ret.set(s_serialNumber, String
          (i2s_ASN1_INTEGER(NULL, X509_get_serialNumber(cert)), AttachString));

  ASN1_STRING *str = X509_get_notBefore(cert);
  ret.set(s_validFrom, String((char*)str->data, str->length, CopyString));
  str = X509_get_notAfter(cert);
  ret.set(s_validTo, String((char*)str->data, str->length, CopyString));
  ret.set(s_validFrom_time_t, asn1_time_to_time_t(X509_get_notBefore(cert)));
  ret.set(s_validTo_time_t, asn1_time_to_time_t(X509_get_notAfter(cert)));

  char *tmpstr = (char *)X509_alias_get0(cert, NULL);
  if (tmpstr) {
    ret.set(s_alias, String(tmpstr, CopyString));
  }

  /* NOTE: the purposes are added as integer keys - the keys match up to
     the X509_PURPOSE_SSL_XXX defines in x509v3.h */
  {
    Array subitem;
    for (int i = 0; i < X509_PURPOSE_get_count(); i++) {
      X509_PURPOSE *purp = X509_PURPOSE_get0(i);
      int id = X509_PURPOSE_get_id(purp);

      Array subsub;
      subsub.append((bool)X509_check_purpose(cert, id, 0));
      subsub.append((bool)X509_check_purpose(cert, id, 1));

      char * pname = shortnames ? X509_PURPOSE_get0_sname(purp) :
        X509_PURPOSE_get0_name(purp);
      subsub.append(String(pname, CopyString));

      subitem.set(id, subsub);
    }
    ret.set(s_purposes, subitem);
  }
  {
    Array subitem;
    for (int i = 0; i < X509_get_ext_count(cert); i++) {
      int nid;
      X509_EXTENSION *extension = X509_get_ext(cert, i);
      char *extname;
      char buf[256];
      nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension));
      if (nid != NID_undef) {
        extname = (char*)OBJ_nid2sn(OBJ_obj2nid
                                    (X509_EXTENSION_get_object(extension)));
      } else {
        OBJ_obj2txt(buf, sizeof(buf)-1, X509_EXTENSION_get_object(extension),
                    1);
        extname = buf;
      }
      BIO *bio_out = BIO_new(BIO_s_mem());
      if (nid == NID_subject_alt_name) {
        if (openssl_x509v3_subjectAltName(bio_out, extension) == 0) {
          BUF_MEM *bio_buf;
          BIO_get_mem_ptr(bio_out, &bio_buf);
          subitem.set(String(extname, CopyString),
                      String((char*)bio_buf->data,
                             bio_buf->length,
                             CopyString));
        } else {
          BIO_free(bio_out);
          return false;
        }
      } else if (X509V3_EXT_print(bio_out, extension, 0, 0)) {
        BUF_MEM *bio_buf;
        BIO_get_mem_ptr(bio_out, &bio_buf);
        subitem.set(String(extname, CopyString),
                    String((char*)bio_buf->data, bio_buf->length, CopyString));
      } else {
        ASN1_STRING *str = X509_EXTENSION_get_data(extension);
        subitem.set(String(extname, CopyString),
                    String((char*)str->data, str->length, CopyString));
      }
      BIO_free(bio_out);
    }
    ret.set(s_extensions, subitem);
  }

  return ret;
}

Variant HHVM_FUNCTION(openssl_x509_read, const Variant& x509certdata) {
  auto ocert = Certificate::Get(x509certdata);
  if (!ocert) {
    raise_warning("supplied parameter cannot be coerced into "
                  "an X509 certificate!");
    return false;
  }
  return Variant(ocert);
}

Variant HHVM_FUNCTION(openssl_random_pseudo_bytes, int length,
                      VRefParam crypto_strong /* = false */) {
  if (length <= 0) {
    return false;
  }

  unsigned char *buffer = NULL;

  String s = String(length, ReserveString);
  buffer = (unsigned char *)s.mutableData();

  if (RAND_bytes(buffer, length) <= 0) {
    crypto_strong.assignIfRef(false);
    return false;
  } else {
    crypto_strong.assignIfRef(true);
    s.setSize(length);
    return s;
  }
}

Variant HHVM_FUNCTION(openssl_cipher_iv_length, const String& method) {
  if (method.empty()) {
    raise_warning("Unknown cipher algorithm");
    return false;
  }

  const EVP_CIPHER *cipher_type = EVP_get_cipherbyname(method.c_str());
  if (!cipher_type) {
    raise_warning("Unknown cipher algorithm");
    return false;
  }

  return EVP_CIPHER_iv_length(cipher_type);
}

static String php_openssl_validate_iv(String piv, int iv_required_len) {
  char *iv_new;

  /* Best case scenario, user behaved */
  if (piv.size() == iv_required_len) {
    return piv;
  }

  String s = String(iv_required_len, ReserveString);
  iv_new = s.mutableData();
  memset(iv_new, 0, iv_required_len);

  if (piv.size() <= 0) {
    /* BC behavior */
    s.setSize(iv_required_len);
    return s;
  }

  if (piv.size() < iv_required_len) {
    raise_warning("IV passed is only %d bytes long, cipher "
                  "expects an IV of precisely %d bytes, padding with \\0",
                  piv.size(), iv_required_len);
    memcpy(iv_new, piv.data(), piv.size());
    s.setSize(iv_required_len);
    return s;
  }

  raise_warning("IV passed is %d bytes long which is longer than the %d "
                "expected by selected cipher, truncating", piv.size(),
                iv_required_len);
  memcpy(iv_new, piv.data(), iv_required_len);
  s.setSize(iv_required_len);
  return s;
}

Variant HHVM_FUNCTION(openssl_encrypt, const String& data, const String& method,
                                       const String& password,
                                       int options /* = 0 */,
                                       const String& iv /* = null_string */) {
  const EVP_CIPHER *cipher_type = EVP_get_cipherbyname(method.c_str());
  if (!cipher_type) {
    raise_warning("Unknown cipher algorithm");
    return false;
  }

  int keylen = EVP_CIPHER_key_length(cipher_type);
  String key = password;

  /*
   * older openssl libraries can assert if the passed in password length is
   * less than keylen
   */
  if (keylen > password.size()) {
    String s = String(keylen, ReserveString);
    char *keybuf = s.mutableData();
    memset(keybuf, 0, keylen);
    memcpy(keybuf, password.data(), password.size());
    key = s.setSize(keylen);
  }

  int max_iv_len = EVP_CIPHER_iv_length(cipher_type);
  if (iv.size() <= 0 && max_iv_len > 0) {
    raise_warning("Using an empty Initialization Vector (iv) is potentially "
                  "insecure and not recommended");
  }

  int result_len = 0;

  String new_iv  = php_openssl_validate_iv(iv, max_iv_len);

  int outlen = data.size() + EVP_CIPHER_block_size(cipher_type);
  String rv = String(outlen, ReserveString);
  unsigned char *outbuf = (unsigned char*)rv.mutableData();

  EVP_CIPHER_CTX cipher_ctx;

  EVP_EncryptInit(&cipher_ctx, cipher_type, NULL, NULL);
  if (password.size() > keylen) {
    EVP_CIPHER_CTX_set_key_length(&cipher_ctx, password.size());
  }
  EVP_EncryptInit_ex(&cipher_ctx, NULL, NULL, (unsigned char *)key.data(),
                  (unsigned char *)new_iv.data());
  if (options & k_OPENSSL_ZERO_PADDING) {
    EVP_CIPHER_CTX_set_padding(&cipher_ctx, 0);
  }

  // OpenSSL before 0.9.8i asserts with size < 0
  if (data.size() > 0) {
    EVP_EncryptUpdate(&cipher_ctx, outbuf, &result_len,
                      (unsigned char *)data.data(), data.size());
  }

  outlen = result_len;

  if (EVP_EncryptFinal(&cipher_ctx, (unsigned char *)outbuf + result_len,
                       &result_len)) {
    outlen += result_len;
    rv.setSize(outlen);
    EVP_CIPHER_CTX_cleanup(&cipher_ctx);
    if (options & k_OPENSSL_RAW_DATA) {
      return rv;
    } else {
      return StringUtil::Base64Encode(rv);
    }
  }

  EVP_CIPHER_CTX_cleanup(&cipher_ctx);
  return false;
}

Variant HHVM_FUNCTION(openssl_decrypt, const String& data, const String& method,
                                       const String& password,
                                       int options /* = 0 */,
                                       const String& iv /* = null_string */) {
  const EVP_CIPHER *cipher_type = EVP_get_cipherbyname(method.c_str());
  if (!cipher_type) {
    raise_warning("Unknown cipher algorithm");
    return false;
  }

  String decoded_data = data;

  if (!(options & k_OPENSSL_RAW_DATA)) {
    decoded_data = StringUtil::Base64Decode(data);
  }

  int keylen = EVP_CIPHER_key_length(cipher_type);
  String key = password;

  /*
   * older openssl libraries can assert if the passed in password length is
   * less than keylen
   */
   if (keylen > password.size()) {
    String s = String(keylen, ReserveString);
    char *keybuf = s.mutableData();
    memset(keybuf, 0, keylen);
    memcpy(keybuf, password.data(), password.size());
    key = s.setSize(keylen);
  }

  int result_len = 0;

  String new_iv  = php_openssl_validate_iv(iv,
                                           EVP_CIPHER_iv_length(cipher_type));

  int outlen = decoded_data.size() + EVP_CIPHER_block_size(cipher_type);
  String rv = String(outlen, ReserveString);
  unsigned char *outbuf = (unsigned char*)rv.mutableData();

  EVP_CIPHER_CTX cipher_ctx;
  EVP_DecryptInit(&cipher_ctx, cipher_type, NULL, NULL);
  if (password.size() > keylen) {
    EVP_CIPHER_CTX_set_key_length(&cipher_ctx, password.size());
  }
  EVP_DecryptInit_ex(&cipher_ctx, NULL, NULL, (unsigned char *)key.data(),
                  (unsigned char *)new_iv.data());
  if (options & k_OPENSSL_ZERO_PADDING) {
    EVP_CIPHER_CTX_set_padding(&cipher_ctx, 0);
  }
  EVP_DecryptUpdate(&cipher_ctx, outbuf, &result_len,
                    (unsigned char *)decoded_data.data(), decoded_data.size());
  outlen = result_len;

  if (EVP_DecryptFinal(&cipher_ctx, (unsigned char *)outbuf + result_len,
                       &result_len)) {
    outlen += result_len;
    EVP_CIPHER_CTX_cleanup(&cipher_ctx);
    rv.setSize(outlen);
    return rv;
  } else {
    EVP_CIPHER_CTX_cleanup(&cipher_ctx);
    return false;
  }
}

Variant HHVM_FUNCTION(openssl_digest, const String& data, const String& method,
                                      bool raw_output /* = false */) {
  const EVP_MD *mdtype = EVP_get_digestbyname(method.c_str());

  if (!mdtype) {
    raise_warning("Unknown signature algorithm");
    return false;
  }
  int siglen = EVP_MD_size(mdtype);
  String rv = String(siglen, ReserveString);
  unsigned char *sigbuf = (unsigned char *)rv.mutableData();
  EVP_MD_CTX md_ctx;

  EVP_DigestInit(&md_ctx, mdtype);
  EVP_DigestUpdate(&md_ctx, (unsigned char *)data.data(), data.size());
  if (EVP_DigestFinal(&md_ctx, (unsigned char *)sigbuf, (unsigned int *)&siglen)) {
    if (raw_output) {
      rv.setSize(siglen);
      return rv;
    } else {
      char* digest_str = string_bin2hex((char*)sigbuf, siglen);
      return String(digest_str, AttachString);
    }
  } else {
    return false;
  }
}

static void openssl_add_method_or_alias(const OBJ_NAME *name, void *arg)
{
  Array *ret = (Array*)arg;
  ret->append(String((char *)name->name, CopyString));
}

static void openssl_add_method(const OBJ_NAME *name, void *arg)
{
  if (name->alias == 0) {
    Array *ret = (Array*)arg;
    ret->append(String((char *)name->name, CopyString));
  }
}

Array HHVM_FUNCTION(openssl_get_cipher_methods, bool aliases /* = false */) {
  Array ret = Array::Create();
  OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH,
    aliases ? openssl_add_method_or_alias: openssl_add_method,
    &ret);
  return ret;
}

Array HHVM_FUNCTION(openssl_get_md_methods, bool aliases /* = false */) {
  Array ret = Array::Create();
  OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_MD_METH,
    aliases ? openssl_add_method_or_alias: openssl_add_method,
    &ret);
  return ret;
}

/////////////////////////////////////////////////////////////////////////////

const StaticString s_OPENSSL_VERSION_TEXT("OPENSSL_VERSION_TEXT");

class opensslExtension final : public Extension {
 public:
  opensslExtension() : Extension("openssl") {}
  void moduleInit() override {
    HHVM_RC_INT(OPENSSL_RAW_DATA, k_OPENSSL_RAW_DATA);
    HHVM_RC_INT(OPENSSL_ZERO_PADDING, k_OPENSSL_ZERO_PADDING);
    HHVM_RC_INT(OPENSSL_NO_PADDING, k_OPENSSL_NO_PADDING);
    HHVM_RC_INT(OPENSSL_PKCS1_OAEP_PADDING, k_OPENSSL_PKCS1_OAEP_PADDING);
    HHVM_RC_INT(OPENSSL_SSLV23_PADDING, k_OPENSSL_SSLV23_PADDING);
    HHVM_RC_INT(OPENSSL_PKCS1_PADDING, k_OPENSSL_PKCS1_PADDING);

    HHVM_RC_INT_SAME(OPENSSL_ALGO_SHA1);
    HHVM_RC_INT_SAME(OPENSSL_ALGO_MD5);
    HHVM_RC_INT_SAME(OPENSSL_ALGO_MD4);
#ifdef HAVE_OPENSSL_MD2_H
    HHVM_RC_INT_SAME(OPENSSL_ALGO_MD2);
#endif
    HHVM_RC_INT_SAME(OPENSSL_ALGO_DSS1);
    HHVM_RC_INT_SAME(OPENSSL_ALGO_SHA224);
    HHVM_RC_INT_SAME(OPENSSL_ALGO_SHA256);
    HHVM_RC_INT_SAME(OPENSSL_ALGO_SHA384);
    HHVM_RC_INT_SAME(OPENSSL_ALGO_SHA512);
    HHVM_RC_INT_SAME(OPENSSL_ALGO_RMD160);

    HHVM_RC_INT(OPENSSL_CIPHER_RC2_40, PHP_OPENSSL_CIPHER_RC2_40);
    HHVM_RC_INT(OPENSSL_CIPHER_RC2_128, PHP_OPENSSL_CIPHER_RC2_128);
    HHVM_RC_INT(OPENSSL_CIPHER_RC2_64, PHP_OPENSSL_CIPHER_RC2_64);
    HHVM_RC_INT(OPENSSL_CIPHER_DES, PHP_OPENSSL_CIPHER_DES);
    HHVM_RC_INT(OPENSSL_CIPHER_3DES, PHP_OPENSSL_CIPHER_3DES);

    HHVM_RC_INT_SAME(OPENSSL_KEYTYPE_RSA);
    HHVM_RC_INT_SAME(OPENSSL_KEYTYPE_DSA);
    HHVM_RC_INT_SAME(OPENSSL_KEYTYPE_DH);
    HHVM_RC_INT_SAME(OPENSSL_KEYTYPE_EC);

    HHVM_RC_INT_SAME(OPENSSL_VERSION_NUMBER);

    HHVM_RC_INT_SAME(PKCS7_TEXT);
    HHVM_RC_INT_SAME(PKCS7_NOCERTS);
    HHVM_RC_INT_SAME(PKCS7_NOSIGS);
    HHVM_RC_INT_SAME(PKCS7_NOCHAIN);
    HHVM_RC_INT_SAME(PKCS7_NOINTERN);
    HHVM_RC_INT_SAME(PKCS7_NOVERIFY);
    HHVM_RC_INT_SAME(PKCS7_DETACHED);
    HHVM_RC_INT_SAME(PKCS7_BINARY);
    HHVM_RC_INT_SAME(PKCS7_NOATTR);

    HHVM_RC_STR_SAME(OPENSSL_VERSION_TEXT);

    HHVM_RC_INT_SAME(X509_PURPOSE_SSL_CLIENT);
    HHVM_RC_INT_SAME(X509_PURPOSE_SSL_SERVER);
    HHVM_RC_INT_SAME(X509_PURPOSE_NS_SSL_SERVER);
    HHVM_RC_INT_SAME(X509_PURPOSE_SMIME_SIGN);
    HHVM_RC_INT_SAME(X509_PURPOSE_SMIME_ENCRYPT);
    HHVM_RC_INT_SAME(X509_PURPOSE_CRL_SIGN);
#ifdef X509_PURPOSE_ANY
    HHVM_RC_INT_SAME(X509_PURPOSE_ANY);
#endif

    HHVM_FE(openssl_csr_export_to_file);
    HHVM_FE(openssl_csr_export);
    HHVM_FE(openssl_csr_get_public_key);
    HHVM_FE(openssl_csr_get_subject);
    HHVM_FE(openssl_csr_new);
    HHVM_FE(openssl_csr_sign);
    HHVM_FE(openssl_error_string);
    HHVM_FE(openssl_open);
    HHVM_FE(openssl_pkcs12_export_to_file);
    HHVM_FE(openssl_pkcs12_export);
    HHVM_FE(openssl_pkcs12_read);
    HHVM_FE(openssl_pkcs7_decrypt);
    HHVM_FE(openssl_pkcs7_encrypt);
    HHVM_FE(openssl_pkcs7_sign);
    HHVM_FE(openssl_pkcs7_verify);
    HHVM_FE(openssl_pkey_export_to_file);
    HHVM_FE(openssl_pkey_export);
    HHVM_FE(openssl_pkey_get_details);
    HHVM_FE(openssl_pkey_get_private);
    HHVM_FE(openssl_pkey_get_public);
    HHVM_FE(openssl_pkey_new);
    HHVM_FE(openssl_private_decrypt);
    HHVM_FE(openssl_private_encrypt);
    HHVM_FE(openssl_public_decrypt);
    HHVM_FE(openssl_public_encrypt);
    HHVM_FE(openssl_seal);
    HHVM_FE(openssl_sign);
    HHVM_FE(openssl_verify);
    HHVM_FE(openssl_x509_check_private_key);
    HHVM_FE(openssl_x509_checkpurpose);
    HHVM_FE(openssl_x509_export_to_file);
    HHVM_FE(openssl_x509_export);
    HHVM_FE(openssl_x509_parse);
    HHVM_FE(openssl_x509_read);
    HHVM_FE(openssl_random_pseudo_bytes);
    HHVM_FE(openssl_cipher_iv_length);
    HHVM_FE(openssl_encrypt);
    HHVM_FE(openssl_decrypt);
    HHVM_FE(openssl_digest);
    HHVM_FE(openssl_get_cipher_methods);
    HHVM_FE(openssl_get_md_methods);

    loadSystemlib();
  }
} s_openssl_extension;

///////////////////////////////////////////////////////////////////////////////
}
