hphp/runtime/ext/openssl/ext_openssl.cpp (2,927 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present 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/array-init.h"
#include "hphp/runtime/base/array-iterator.h"
#include "hphp/runtime/base/ssl-socket.h"
#include "hphp/runtime/base/string-util.h"
#include "hphp/runtime/base/zend-string.h"
#include "hphp/util/logger.h"
#include <folly/ScopeGuard.h>
#include <openssl/conf.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h>
#include <openssl/rand.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];
struct OpenSSLInitializer {
OpenSSLInitializer() {
SSL_library_init();
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
OpenSSL_add_all_algorithms();
// CCM ciphers are not added by default, so let's add them!
#if !defined(OPENSSL_NO_AES) && defined(EVP_CIPH_CCM_MODE) && \
OPENSSL_VERSION_NUMBER < 0x100020000
EVP_add_cipher(EVP_aes_128_ccm());
EVP_add_cipher(EVP_aes_192_ccm());
EVP_add_cipher(EVP_aes_256_ccm());
#endif
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 == nullptr) {
config_filename = getenv("SSLEAY_CONF");
}
/* default to 'openssl.cnf' if no environment variable is set */
if (config_filename == nullptr) {
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
struct Key : SweepableResourceData {
EVP_PKEY *m_key;
explicit Key(EVP_PKEY *key) : m_key(key) { assertx(m_key);}
~Key() override {
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() {
assertx(m_key);
switch (EVP_PKEY_id(m_key)) {
#ifndef NO_RSA
case EVP_PKEY_RSA:
case EVP_PKEY_RSA2:
{
const auto rsa = EVP_PKEY_get0_RSA(m_key);
assertx(rsa);
const BIGNUM *p, *q;
RSA_get0_factors(rsa, &p, &q);
if (!p || !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:
{
const auto dsa = EVP_PKEY_get0_DSA(m_key);
assertx(dsa);
const BIGNUM *p, *q, *g, *pub_key, *priv_key;
DSA_get0_pqg(dsa, &p, &q, &g);
if (!p || !q || !g) {
return false;
}
DSA_get0_key(dsa, &pub_key, &priv_key);
if (!priv_key) {
return false;
}
break;
}
#endif
#ifndef NO_DH
case EVP_PKEY_DH:
{
const auto dh = EVP_PKEY_get0_DH(m_key);
assertx(dh);
const BIGNUM *p, *q, *g, *pub_key, *priv_key;
DH_get0_pqg(dh, &p, &q, &g);
if (!p) {
return false;
}
DH_get0_key(dh, &pub_key, &priv_key);
if (!priv_key) {
return false;
}
break;
}
#endif
#ifdef HAVE_EVP_PKEY_EC
case EVP_PKEY_EC:
{
const auto ec_key = EVP_PKEY_get0_EC_KEY(m_key);
assertx(ec_key);
if (EC_KEY_get0_private_key(ec_key) == nullptr) {
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 nullptr for the
* passphrase - nullptr 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 = nullptr;
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, nullptr,nullptr, nullptr);
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, nullptr,nullptr, (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
*/
struct CSRequest : SweepableResourceData {
private:
X509_REQ *m_csr;
public:
explicit CSRequest(X509_REQ *csr) : m_csr(csr) {
assertx(m_csr);
}
X509_REQ *csr() { return m_csr; }
~CSRequest() override {
// 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 nullptr;
}
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, nullptr,nullptr,nullptr);
BIO_free(in);
if (csr) {
return req::make<CSRequest>(csr);
}
}
return nullptr;
}
};
IMPLEMENT_RESOURCE_ALLOCATION(CSRequest)
struct php_x509_request {
#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;
#ifdef HAVE_EVP_PKEY_EC
int curve_name;
#endif
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 == nullptr) {
file = RAND_file_name(buffer, sizeof(buffer));
#if !defined(OPENSSL_NO_RAND_EGD) && !defined(OPENSSL_NO_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 == nullptr || !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 == nullptr) {
file = RAND_file_name(buffer, sizeof(buffer));
}
if (file == nullptr || !RAND_write_file(file)) {
raise_warning("unable to write random state");
return false;
}
return true;
}
bool generatePrivateKey() {
assertx(priv_key == nullptr);
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()) != nullptr) {
switch (priv_key_type) {
case OPENSSL_KEYTYPE_RSA:
if (EVP_PKEY_assign_RSA
(priv_key, RSA_generate_key(priv_key_bits, 0x10001, nullptr, nullptr))) {
ret = true;
}
break;
#if !defined(NO_DSA) && defined(HAVE_DSA_DEFAULT_METHOD)
case OPENSSL_KEYTYPE_DSA:
{
DSA *dsapar = DSA_generate_parameters(priv_key_bits, nullptr, 0, nullptr,
nullptr, nullptr, nullptr);
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
#ifdef HAVE_EVP_PKEY_EC
case OPENSSL_KEYTYPE_EC:
{
if (curve_name == NID_undef) {
raise_warning("Missing configuration value: 'curve_name' not set");
return false;
}
if (auto const eckey = EC_KEY_new_by_curve_name(curve_name)) {
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
if (EC_KEY_generate_key(eckey) &&
EVP_PKEY_assign_EC_KEY(priv_key, eckey)) {
ret = true;
} else {
EC_KEY_free(eckey);
}
}
}
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 = nullptr;
unsigned char *to_add = nullptr;
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, nullptr, "oid_section");
if (str == nullptr) {
return true;
}
STACK_OF(CONF_VALUE) *sktmp = CONF_get_section(req->req_config, str);
if (sktmp == nullptr) {
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_sn2nid(cnf->name) == NID_undef &&
OBJ_ln2nid(cnf->name) == NID_undef &&
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, nullptr)) {
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"),
s_curve_name("curve_name");
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(nullptr, default_ssl_conf_filename, nullptr);
req->req_config = CONF_load(nullptr, req->config_filename, nullptr);
if (req->req_config == nullptr) {
return false;
}
/* read in the oids */
char *str = CONF_get_string(req->req_config, nullptr, "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 == nullptr) {
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 == nullptr) {
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 == nullptr) {
req->md_alg = req->digest = EVP_sha256();
}
#ifdef HAVE_EVP_PKEY_EC
/* set the ec group curve name */
req->curve_name = NID_undef;
if (args.exists(s_curve_name)) {
auto const curve_name = args[s_curve_name].toString();
req->curve_name = OBJ_sn2nid(curve_name.data());
if (req->curve_name == NID_undef) {
raise_warning(
"Unknown elliptic curve (short) name %s",
curve_name.data()
);
return false;
}
}
#endif
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 = nullptr;
}
if (req->req_config) {
CONF_free(req->req_config);
req->req_config = nullptr;
}
}
static STACK_OF(X509) *load_all_certs_from_file(const char *certfile) {
STACK_OF(X509_INFO) *sk = nullptr;
STACK_OF(X509) *stack = nullptr, *ret = nullptr;
BIO *in = nullptr;
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, nullptr, nullptr, nullptr))) {
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 != nullptr) {
sk_X509_push(stack, xi->x509);
xi->x509 = nullptr;
}
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 == nullptr) {
return nullptr;
}
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 == nullptr ||
!X509_LOOKUP_load_file(file_lookup, item.data(),
X509_FILETYPE_PEM)) {
raise_warning("error loading file %s", item.data());
} else {
nfiles++;
}
file_lookup = nullptr;
} else {
dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
if (dir_lookup == nullptr ||
!X509_LOOKUP_add_dir(dir_lookup, item.data(), X509_FILETYPE_PEM)) {
raise_warning("error loading directory %s", item.data());
} else {
ndirs++;
}
dir_lookup = nullptr;
}
}
if (nfiles == 0) {
file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
if (file_lookup) {
X509_LOOKUP_load_file(file_lookup, nullptr, 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, nullptr, X509_FILETYPE_DEFAULT);
}
}
return store;
}
///////////////////////////////////////////////////////////////////////////////
static bool add_entries(X509_NAME *subj, const Array& items) {
for (ArrayIter iter(items); iter; ++iter) {
auto const index = iter.first().toString();
auto const item = iter.second().toString();
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 == nullptr) return false;
STACK_OF(CONF_VALUE) *dn_sk = CONF_get_section(req->req_config, dn_sect);
if (dn_sk == nullptr) return false;
char *attr_sect = CONF_get_string(req->req_config, req->section_name,
"attributes");
STACK_OF(CONF_VALUE) *attr_sk = nullptr;
if (attr_sect) {
attr_sk = CONF_get_section(req->req_config, attr_sect);
if (attr_sk == nullptr) {
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 == nullptr) {
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, Variant& 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 = 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;
auto input_csr = pcsr->csr();
#if OPENSSL_VERSION_NUMBER >= 0x10100000
/* Due to changes in OpenSSL 1.1 related to locking when decoding CSR,
* the pub key is not changed after assigning. It means if we pass
* a private key, it will be returned including the private part.
* If we duplicate it, then we get just the public part which is
* the same behavior as for OpenSSL 1.0 */
input_csr = X509_REQ_dup(input_csr);
/* We need to free the CSR as it was duplicated */
SCOPE_EXIT { X509_REQ_free(input_csr); };
#endif
auto pubkey = X509_REQ_get_pubkey(input_csr);
if (!pubkey) return false;
return Variant(req::make<Key>(pubkey));
}
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::CreateDict();
add_assoc_name_entry(ret, nullptr, subject, use_shortnames);
return ret;
}
Variant HHVM_FUNCTION(openssl_csr_new,
const Variant& dn, Variant& privkey,
const Variant& configargs /* = uninit_variant */,
const Variant& extraattribs /* = uninit_variant */) {
Variant ret = false;
struct php_x509_request req;
memset(&req, 0, sizeof(req));
req::ptr<Key> okey;
X509_REQ *csr = nullptr;
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 == nullptr) {
req.generatePrivateKey();
if (req.priv_key) {
okey = req::make<Key>(req.priv_key);
}
}
if (req.priv_key == nullptr) {
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, nullptr, nullptr, csr, nullptr, 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 = nullptr;
} else {
raise_warning("Error signing request");
}
privkey = 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 = nullptr;
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 == nullptr) {
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 == nullptr) {
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 == nullptr) {
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, Variant& open_data,
const String& env_key,
const Variant& priv_key_id,
const String& method, /* = null_string */
const String& iv /* = 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;
const unsigned char *iv_buf = nullptr;
int iv_len = EVP_CIPHER_iv_length(cipher_type);
if (iv_len > 0) {
if (iv.empty()) {
raise_warning(
"Cipher algorithm requires an IV to be supplied as a sixth parameter");
return false;
}
if (iv.length() != iv_len) {
raise_warning("IV length is invalid");
return false;
}
iv_buf = reinterpret_cast<const unsigned char*>(iv.c_str());
}
String s = String(sealed_data.size(), ReserveString);
unsigned char *buf = (unsigned char *)s.mutableData();
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (ctx == nullptr) {
raise_warning("Failed to allocate an EVP_CIPHER_CTX object");
return false;
}
SCOPE_EXIT {
EVP_CIPHER_CTX_free(ctx);
};
int len1, len2;
if (!EVP_OpenInit(
ctx,
cipher_type,
(unsigned char*)env_key.data(),
env_key.size(),
iv_buf,
pkey) ||
!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 = 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 /* = uninit_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 = nullptr;
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() ? nullptr : friendly_name.data()),
key, cert, ca, 0, 0, 0, 0, 0);
assertx(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 /* = uninit_variant */) {
BIO *bio_out = BIO_new_file(filename.data(), "w");
if (bio_out == nullptr) {
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, Variant& out,
const Variant& priv_key,
const String& pass,
const Variant& args /* = uninit_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 = 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, Variant& certs,
const String& pass) {
bool ret = false;
PKCS12 *p12 = nullptr;
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 = nullptr;
X509 *cert = nullptr;
STACK_OF(X509) *ca = nullptr;
if (PKCS12_parse(p12, pass.data(), &pkey, &cert, &ca)) {
Variant vcerts = Array::CreateDict();
SCOPE_EXIT {
certs = vcerts;
};
BIO *bio_out = nullptr;
if (cert) {
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.asArrRef().set(s_cert,
String((char*)bio_buf->data, bio_buf->length, CopyString));
}
BIO_free(bio_out);
}
if (pkey) {
bio_out = BIO_new(BIO_s_mem());
if (PEM_write_bio_PrivateKey(bio_out, pkey, nullptr, nullptr, 0, 0, nullptr)) {
BUF_MEM *bio_buf;
BIO_get_mem_ptr(bio_out, &bio_buf);
vcerts.asArrRef().set(s_pkey,
String((char*)bio_buf->data, bio_buf->length, CopyString));
}
BIO_free(bio_out);
}
if (ca) {
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);
}
sk_X509_free(ca);
vcerts.asArrRef().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 /* = uninit_variant */) {
bool ret = false;
BIO *in = nullptr, *out = nullptr, *datain = nullptr;
PKCS7 *p7 = nullptr;
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 == nullptr) {
raise_warning("error opening the file, %s", infilename.data());
goto clean_exit;
}
out = BIO_new_file(outfilename.data(), "w");
if (out == nullptr) {
raise_warning("error opening the file, %s", outfilename.data());
goto clean_exit;
}
p7 = SMIME_read_PKCS7(in, &datain);
if (p7 == nullptr) {
goto clean_exit;
}
assertx(okey->m_key);
assertx(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 = nullptr, *outfile = nullptr;
STACK_OF(X509) *precipcerts = nullptr;
PKCS7 *p7 = nullptr;
const EVP_CIPHER *cipher = nullptr;
infile = BIO_new_file(infilename.data(), (flags & PKCS7_BINARY) ? "rb" : "r");
if (infile == nullptr) {
raise_warning("error opening the file, %s", infilename.data());
goto clean_exit;
}
outfile = BIO_new_file(outfilename.data(), "w");
if (outfile == nullptr) {
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 == nullptr) {
raise_warning("Failed to get cipher");
goto clean_exit;
}
p7 = PKCS7_encrypt(precipcerts, infile, (EVP_CIPHER*)cipher, flags);
if (p7 == nullptr) 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 = nullptr;
BIO *infile = nullptr, *outfile = nullptr;
PKCS7 *p7 = nullptr;
req::ptr<Key> okey;
req::ptr<Certificate> ocert;
if (!extracerts.empty()) {
others = load_all_certs_from_file(extracerts.data());
if (others == nullptr) {
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(), (flags & PKCS7_BINARY) ? "rb" : "r");
if (infile == nullptr) {
raise_warning("error opening input file %s!", infilename.data());
goto clean_exit;
}
outfile = BIO_new_file(outfilename.data(), "w");
if (outfile == nullptr) {
raise_warning("error opening output file %s!", outfilename.data());
goto clean_exit;
}
p7 = PKCS7_sign(cert, key, others, infile, flags);
if (p7 == nullptr) {
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 = nullptr;
BIO *in = nullptr;
PKCS7 *p7 = nullptr;
BIO *datain = nullptr;
BIO *dataout = nullptr;
auto cainfo = vcainfo.toArray();
auto extracerts = vextracerts.toString();
auto content = vcontent.toString();
STACK_OF(X509) *others = nullptr;
if (!extracerts.empty()) {
others = load_all_certs_from_file(extracerts.data());
if (others == nullptr) {
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
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
assertx(!X509_STORE_get_verify_cb(store));
#else
assertx(!store->verify_cb);
#endif
// 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 == nullptr) {
raise_warning("error opening the file, %s", filename.data());
goto clean_exit;
}
p7 = SMIME_read_PKCS7(in, &datain);
if (p7 == nullptr) {
goto clean_exit;
}
if (!content.empty()) {
dataout = BIO_new_file(content.data(), "w");
if (dataout == nullptr) {
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, nullptr, 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_pop_free(others, X509_free);
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 /* = uninit_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 = nullptr;
}
assertx(bio_out);
ret = PEM_write_bio_PrivateKey(bio_out, pkey, cipher,
(unsigned char *)passphrase.data(),
passphrase.size(), nullptr, nullptr);
}
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 /* = uninit_variant */) {
BIO *bio_out = BIO_new_file(outfilename.data(), "w");
if (bio_out == nullptr) {
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, Variant& out,
const String& passphrase /* = null_string */,
const Variant& configargs /* = uninit_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 = 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_signatureAlgorithm("signatureAlgorithm"),
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_ec("ec"),
s_n("n"),
s_e("e"),
s_d("d"),
s_p("p"),
s_q("q"),
s_g("g"),
s_x("x"),
s_y("y"),
s_dmp1("dmp1"),
s_dmq1("dmq1"),
s_iqmp("iqmp"),
s_priv_key("priv_key"),
s_pub_key("pub_key"),
s_curve_oid("curve_oid");
static void add_bignum_as_string(Array &arr,
StaticString key,
const BIGNUM *bn) {
if (!bn) {
return;
}
int num_bytes = BN_num_bytes(bn);
String str{size_t(num_bytes), ReserveString};
BN_bn2bin(bn, (unsigned char*)str.mutableData());
str.setSize(num_bytes);
arr.set(key, std::move(str));
}
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);
auto ret = make_dict_array(
s_bits, EVP_PKEY_bits(pkey),
s_key, String(pbio, pbio_len, CopyString)
);
long ktype = -1;
auto details = Array::CreateDict();
switch (EVP_PKEY_id(pkey)) {
case EVP_PKEY_RSA:
case EVP_PKEY_RSA2:
{
ktype = OPENSSL_KEYTYPE_RSA;
RSA *rsa = EVP_PKEY_get0_RSA(pkey);
assertx(rsa);
const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp;
RSA_get0_key(rsa, &n, &e, &d);
RSA_get0_factors(rsa, &p, &q);
RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
add_bignum_as_string(details, s_n, n);
add_bignum_as_string(details, s_e, e);
add_bignum_as_string(details, s_d, d);
add_bignum_as_string(details, s_p, p);
add_bignum_as_string(details, s_q, q);
add_bignum_as_string(details, s_dmp1, dmp1);
add_bignum_as_string(details, s_dmq1, dmq1);
add_bignum_as_string(details, s_iqmp, 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;
DSA *dsa = EVP_PKEY_get0_DSA(pkey);
assertx(dsa);
const BIGNUM *p, *q, *g, *pub_key, *priv_key;
DSA_get0_pqg(dsa, &p, &q, &g);
DSA_get0_key(dsa, &pub_key, &priv_key);
add_bignum_as_string(details, s_p, p);
add_bignum_as_string(details, s_q, q);
add_bignum_as_string(details, s_g, g);
add_bignum_as_string(details, s_priv_key, priv_key);
add_bignum_as_string(details, s_pub_key, pub_key);
ret.set(s_dsa, details);
break;
}
case EVP_PKEY_DH:
{
ktype = OPENSSL_KEYTYPE_DH;
DH *dh = EVP_PKEY_get0_DH(pkey);
assertx(dh);
const BIGNUM *p, *q, *g, *pub_key, *priv_key;
DH_get0_pqg(dh, &p, &q, &g);
DH_get0_key(dh, &pub_key, &priv_key);
add_bignum_as_string(details, s_p, p);
add_bignum_as_string(details, s_g, g);
add_bignum_as_string(details, s_priv_key, priv_key);
add_bignum_as_string(details, s_pub_key, pub_key);
ret.set(s_dh, details);
break;
}
#ifdef HAVE_EVP_PKEY_EC
case EVP_PKEY_EC:
{
ktype = OPENSSL_KEYTYPE_EC;
auto const ec = EVP_PKEY_get0_EC_KEY(pkey);
assertx(ec);
auto const ec_group = EC_KEY_get0_group(ec);
auto const nid = EC_GROUP_get_curve_name(ec_group);
if (nid == NID_undef) {
break;
}
auto const crv_sn = OBJ_nid2sn(nid);
if (crv_sn != nullptr) {
details.set(s_curve_name, String(crv_sn, CopyString));
}
auto const obj = OBJ_nid2obj(nid);
if (obj != nullptr) {
SCOPE_EXIT {
ASN1_OBJECT_free(obj);
};
char oir_buf[256];
OBJ_obj2txt(oir_buf, sizeof(oir_buf) - 1, obj, 1);
details.set(s_curve_oid, String(oir_buf, CopyString));
}
auto x = BN_new();
auto y = BN_new();
SCOPE_EXIT {
BN_free(x);
BN_free(y);
};
auto const pub = EC_KEY_get0_public_key(ec);
if (EC_POINT_get_affine_coordinates_GFp(ec_group, pub, x, y, nullptr)) {
add_bignum_as_string(details, s_x, x);
add_bignum_as_string(details, s_y, y);
}
auto d = BN_dup(EC_KEY_get0_private_key(ec));
SCOPE_EXIT {
BN_free(d);
};
if (d != nullptr) {
add_bignum_as_string(details, s_d, d);
}
ret.set(s_ec, details);
}
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));
}
Variant HHVM_FUNCTION(openssl_pkey_new,
const Variant& configargs /* = uninit_variant */) {
struct php_x509_request req;
memset(&req, 0, sizeof(req));
SCOPE_EXIT {
php_openssl_dispose_config(&req);
};
std::vector<String> strings;
if (php_openssl_parse_config(&req, configargs.toArray(), strings) &&
req.generatePrivateKey()) {
return Resource(req::make<Key>(req.priv_key));
} else {
return false;
}
}
bool HHVM_FUNCTION(openssl_private_decrypt, const String& data,
Variant& 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 (EVP_PKEY_id(pkey)) {
case EVP_PKEY_RSA:
case EVP_PKEY_RSA2:
cryptedlen = RSA_private_decrypt(data.size(),
(unsigned char *)data.data(),
cryptedbuf,
EVP_PKEY_get0_RSA(pkey),
padding);
if (cryptedlen != -1) {
successful = 1;
}
break;
default:
raise_warning("key type not supported");
}
if (successful) {
decrypted = s.setSize(cryptedlen);
return true;
}
return false;
}
bool HHVM_FUNCTION(openssl_private_encrypt, const String& data,
Variant& 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 (EVP_PKEY_id(pkey)) {
case EVP_PKEY_RSA:
case EVP_PKEY_RSA2:
successful = (RSA_private_encrypt(data.size(),
(unsigned char *)data.data(),
cryptedbuf,
EVP_PKEY_get0_RSA(pkey),
padding) == cryptedlen);
break;
default:
raise_warning("key type not supported");
}
if (successful) {
crypted = s.setSize(cryptedlen);
return true;
}
return false;
}
bool HHVM_FUNCTION(openssl_public_decrypt, const String& data,
Variant& 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 (EVP_PKEY_id(pkey)) {
case EVP_PKEY_RSA:
case EVP_PKEY_RSA2:
cryptedlen = RSA_public_decrypt(data.size(),
(unsigned char *)data.data(),
cryptedbuf,
EVP_PKEY_get0_RSA(pkey),
padding);
if (cryptedlen != -1) {
successful = 1;
}
break;
default:
raise_warning("key type not supported");
}
if (successful) {
decrypted = s.setSize(cryptedlen);
return true;
}
return false;
}
bool HHVM_FUNCTION(openssl_public_encrypt, const String& data,
Variant& 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 (EVP_PKEY_id(pkey)) {
case EVP_PKEY_RSA:
case EVP_PKEY_RSA2:
successful = (RSA_public_encrypt(data.size(),
(unsigned char *)data.data(),
cryptedbuf,
EVP_PKEY_get0_RSA(pkey),
padding) == cryptedlen);
break;
default:
raise_warning("key type not supported");
}
if (successful) {
crypted = s.setSize(cryptedlen);
return true;
}
return false;
}
Variant HHVM_FUNCTION(openssl_seal, const String& data, Variant& sealed_data,
Variant& env_keys,
const Array& pub_key_ids,
const String& method,
Variant& iv) {
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;
}
}
int iv_len = EVP_CIPHER_iv_length(cipher_type);
unsigned char *iv_buf = nullptr;
String iv_s;
if (iv_len > 0) {
iv_s = String(iv_len, ReserveString);
iv_buf = (unsigned char*)iv_s.mutableData();
if (!RAND_bytes(iv_buf, iv_len)) {
raise_warning("Could not generate an IV.");
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 = nullptr;
EVP_CIPHER_CTX* ctx = nullptr;
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);
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == nullptr) {
raise_warning("Failed to allocate an EVP_CIPHER_CTX object");
ret = false;
goto clean_exit;
}
if (!EVP_EncryptInit_ex(ctx, cipher_type, nullptr, nullptr, nullptr)) {
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, iv_buf, pkeys, nkeys) <= 0 ||
!EVP_SealUpdate(ctx, buf, &len1, (unsigned char*)data.data(), data.size()) ||
!EVP_SealFinal(ctx, buf + len1, &len2)) {
ret = false;
goto clean_exit;
}
if (len1 + len2 > 0) {
sealed_data = s.setSize(len1 + len2);
auto ekeys = Array::CreateVec();
for (i = 0; i < nkeys; i++) {
eks[i][eksl[i]] = '\0';
ekeys.append(String((char*)eks[i], eksl[i], AttachString));
eks[i] = nullptr;
}
env_keys = ekeys;
}
clean_exit:
for (i = 0; i < nkeys; i++) {
if (eks[i]) free(eks[i]);
}
free(eks);
free(eksl);
free(pkeys);
if (iv_buf != nullptr) {
if (ret) {
iv = iv_s.setSize(iv_len);
}
}
if (ctx != nullptr) {
EVP_CIPHER_CTX_free(ctx);
}
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
#if OPENSSL_VERSION_NUMBER < 0x10100000L
case OPENSSL_ALGO_DSS1: return EVP_dss1();
#endif
#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 nullptr;
}
bool HHVM_FUNCTION(openssl_sign, const String& data, Variant& 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_MD_CTX_new();
SCOPE_EXIT {
EVP_MD_CTX_free(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 = s.setSize(siglen);
return true;
}
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_MD_CTX_new();
SCOPE_EXIT {
EVP_MD_CTX_free(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);
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 == nullptr) {
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 = nullptr;
X509_STORE *pcainfo = nullptr;
req::ptr<Certificate> ocert;
if (!untrustedfile.empty()) {
untrustedchain = load_all_certs_from_file(untrustedfile.data());
if (untrustedchain == nullptr) {
goto clean_exit;
}
}
pcainfo = setup_verify(cainfo);
if (pcainfo == nullptr) {
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;
assertx(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;
assertx(cert);
assertx(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 == nullptr) {
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, Variant& 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 = 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) {
auto const timestr_type = ASN1_STRING_type(timestr);
if (timestr_type != V_ASN1_UTCTIME && timestr_type != V_ASN1_GENERALIZEDTIME) {
raise_warning("illegal ASN1 data type for timestamp");
return (time_t)-1;
}
auto const timestr_len = (size_t)ASN1_STRING_length(timestr);
// Binary safety
if (timestr_len != strlen((char*)ASN1_STRING_data(timestr))) {
raise_warning("illegal length in timestamp");
return (time_t)-1;
}
if (timestr_len < 13 && timestr_len != 11) {
raise_warning("unable to parse time string %s correctly",
timestr->data);
return (time_t)-1;
}
if (timestr_type == V_ASN1_GENERALIZEDTIME && timestr_len < 15) {
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;
if (ASN1_STRING_length(timestr) == 11) {
thetime.tm_sec = 0;
} else {
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';
if (ASN1_STRING_type(timestr) == V_ASN1_UTCTIME) {
thestr -= 2;
thetime.tm_year = atoi(thestr);
if (thetime.tm_year < 68) {
thetime.tm_year += 100;
}
} else if (ASN1_STRING_type(timestr) == V_ASN1_GENERALIZEDTIME) {
thestr -= 4;
thetime.tm_year = atoi(thestr) - 1900;
}
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);
#endif
/* no adjustment for UTC */
if (timezone) 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;
}
const auto data = X509_EXTENSION_get_data(extension);
p = data->data;
length = data->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;
assertx(cert);
auto ret = Array::CreateDict();
const auto sn = X509_get_subject_name(cert);
if (sn) {
ret.set(s_name, String(X509_NAME_oneline(sn, nullptr, 0), CopyString));
}
add_assoc_name_entry(ret, "subject", sn, 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(nullptr, X509_get_serialNumber(cert)), AttachString));
// Adding Signature Algorithm
BIO *bio_out = BIO_new(BIO_s_mem());
SCOPE_EXIT { BIO_free(bio_out); };
if (i2a_ASN1_OBJECT(bio_out, X509_get0_tbs_sigalg(cert)->algorithm) > 0) {
BUF_MEM *bio_buf;
BIO_get_mem_ptr(bio_out, &bio_buf);
ret.set(s_signatureAlgorithm,
String((char*)bio_buf->data, bio_buf->length, CopyString));
}
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, nullptr);
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);
char * pname = shortnames ? X509_PURPOSE_get0_sname(purp) :
X509_PURPOSE_get0_name(purp);
auto subsub = make_vec_array(
(bool)X509_check_purpose(cert, id, 0),
(bool)X509_check_purpose(cert, id, 1),
String(pname, CopyString)
);
subitem.set(id, std::move(subsub));
}
ret.set(s_purposes, subitem);
}
{
auto subitem = Array::CreateDict();
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 {
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,
bool& crypto_strong) {
if (length <= 0) {
return false;
}
unsigned char *buffer = nullptr;
String s = String(length, ReserveString);
buffer = (unsigned char *)s.mutableData();
if (RAND_bytes(buffer, length) <= 0) {
crypto_strong = false;
return false;
} else {
crypto_strong = 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);
}
/* Cipher mode info */
struct php_openssl_cipher_mode {
/* Whether this mode uses authenticated encryption. True, for example, with
the GCM and CCM modes */
bool is_aead;
/* Whether this mode is a 'single run aead', meaning that DecryptFinal doesn't
get called. For example, CCM mode is a single run aead mode. */
bool is_single_run_aead;
/* The OpenSSL flag to get the computed tag, if this mode is aead. */
int aead_get_tag_flag;
/* The OpenSSL flag to set the computed tag, if this mode is aead. */
int aead_set_tag_flag;
/* The OpenSSL flag to set the IV length, if this mode is aead */
int aead_ivlen_flag;
};
// initialize a php_openssl_cipher_mode corresponding to an EVP_CIPHER.
static php_openssl_cipher_mode php_openssl_load_cipher_mode(
const EVP_CIPHER* cipher_type) {
php_openssl_cipher_mode mode = {};
switch (EVP_CIPHER_mode(cipher_type)) {
#ifdef EVP_CIPH_GCM_MODE
case EVP_CIPH_GCM_MODE:
mode.is_aead = true;
mode.is_single_run_aead = false;
mode.aead_get_tag_flag = EVP_CTRL_GCM_GET_TAG;
mode.aead_set_tag_flag = EVP_CTRL_GCM_SET_TAG;
mode.aead_ivlen_flag = EVP_CTRL_GCM_SET_IVLEN;
break;
#endif
#ifdef EVP_CIPH_CCM_MODE
case EVP_CIPH_CCM_MODE:
mode.is_aead = true;
mode.is_single_run_aead = true;
mode.aead_get_tag_flag = EVP_CTRL_CCM_GET_TAG;
mode.aead_set_tag_flag = EVP_CTRL_CCM_SET_TAG;
mode.aead_ivlen_flag = EVP_CTRL_CCM_SET_IVLEN;
break;
#endif
default:
break;
}
return mode;
}
static bool php_openssl_validate_iv(
String piv,
int iv_required_len,
String& out,
EVP_CIPHER_CTX* cipher_ctx,
const php_openssl_cipher_mode* mode) {
if (cipher_ctx == nullptr || mode == nullptr) {
return false;
}
/* Best case scenario, user behaved */
if (piv.size() == iv_required_len) {
out = std::move(piv);
return true;
}
if (mode->is_aead) {
if (EVP_CIPHER_CTX_ctrl(
cipher_ctx, mode->aead_ivlen_flag, piv.size(), nullptr) != 1) {
raise_warning(
"Setting of IV length for AEAD mode failed, the expected length is "
"%d bytes",
iv_required_len);
return false;
}
out = std::move(piv);
return true;
}
String s = String(iv_required_len, ReserveString);
char* iv_new = s.mutableData();
memset(iv_new, 0, iv_required_len);
if (piv.size() <= 0) {
/* BC behavior */
s.setSize(iv_required_len);
out = std::move(s);
return true;
}
if (piv.size() < iv_required_len) {
raise_warning("IV passed is only %ld 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);
out = std::move(s);
return true;
}
raise_warning("IV passed is %ld 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);
out = std::move(s);
return true;
}
namespace {
Variant openssl_encrypt_impl(const String& data,
const String& method,
const String& password,
int options,
const String& iv,
Variant* tag_out,
const String& aad,
int tag_length) {
const EVP_CIPHER *cipher_type = EVP_get_cipherbyname(method.c_str());
if (!cipher_type) {
raise_warning("Unknown cipher algorithm");
return false;
}
EVP_CIPHER_CTX* cipher_ctx = EVP_CIPHER_CTX_new();
if (!cipher_ctx) {
raise_warning("Failed to create cipher context");
return false;
}
SCOPE_EXIT {
EVP_CIPHER_CTX_free(cipher_ctx);
};
php_openssl_cipher_mode mode = php_openssl_load_cipher_mode(cipher_type);
if (mode.is_aead && !tag_out) {
raise_warning("Must call openssl_encrypt_with_tag when using an AEAD cipher");
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 && !mode.is_aead) {
raise_warning("Using an empty Initialization Vector (iv) is potentially "
"insecure and not recommended");
}
int result_len = 0;
int outlen = data.size() + EVP_CIPHER_block_size(cipher_type);
String rv = String(outlen, ReserveString);
unsigned char *outbuf = (unsigned char*)rv.mutableData();
EVP_EncryptInit_ex(cipher_ctx, cipher_type, nullptr, nullptr, nullptr);
String new_iv;
// we do this after EncryptInit because validate_iv changes cipher_ctx for
// aead modes (must be initialized first).
if (!php_openssl_validate_iv(
std::move(iv), max_iv_len, new_iv, cipher_ctx, &mode)) {
return false;
}
// set the tag length for CCM mode/other modes that require tag lengths to
// be set.
if (mode.is_single_run_aead &&
!EVP_CIPHER_CTX_ctrl(
cipher_ctx, mode.aead_set_tag_flag, tag_length, nullptr)) {
raise_warning("Setting tag length failed");
return false;
}
if (password.size() > keylen) {
EVP_CIPHER_CTX_set_key_length(cipher_ctx, password.size());
}
EVP_EncryptInit_ex(
cipher_ctx,
nullptr,
nullptr,
(unsigned char*)key.data(),
(unsigned char*)new_iv.data());
if (options & k_OPENSSL_ZERO_PADDING) {
EVP_CIPHER_CTX_set_padding(cipher_ctx, 0);
}
// for single run aeads like CCM, we need to provide the length of the
// plaintext before providing AAD or ciphertext.
if (mode.is_single_run_aead &&
!EVP_EncryptUpdate(
cipher_ctx, nullptr, &result_len, nullptr, data.size())) {
raise_warning("Setting of data length failed");
return false;
}
// set up aad:
if (mode.is_aead &&
!EVP_EncryptUpdate(
cipher_ctx,
nullptr,
&result_len,
(unsigned char*)aad.data(),
aad.size())) {
raise_warning("Setting of additional application data failed");
return false;
}
// 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_ex(
cipher_ctx, (unsigned char*)outbuf + result_len, &result_len)) {
outlen += result_len;
rv.setSize(outlen);
// Get tag if possible
if (mode.is_aead) {
String tagrv = String(tag_length, ReserveString);
if (EVP_CIPHER_CTX_ctrl(
cipher_ctx,
mode.aead_get_tag_flag,
tag_length,
tagrv.mutableData()) == 1) {
tagrv.setSize(tag_length);
assertx(tag_out);
*tag_out = tagrv;
} else {
raise_warning("Retrieving authentication tag failed");
return false;
}
} else if (tag_out) {
raise_warning(
"The authenticated tag cannot be provided for cipher that does not"
" support AEAD");
}
// Return encrypted data
if (options & k_OPENSSL_RAW_DATA) {
return rv;
} else {
return StringUtil::Base64Encode(rv);
}
}
return false;
}
} // anonymous namespace
Variant HHVM_FUNCTION(openssl_encrypt,
const String& data,
const String& method,
const String& password,
int options /* = 0 */,
const String& iv /* = null_string */,
const String& aad /* = null_string */,
int tag_length /* = 16 */) {
return openssl_encrypt_impl(data, method, password, options, iv,
nullptr, aad, tag_length);
}
Variant HHVM_FUNCTION(openssl_encrypt_with_tag,
const String& data,
const String& method,
const String& password,
int options,
const String& iv,
Variant& tag_out,
const String& aad /* = null_string */,
int tag_length /* = 16 */) {
return openssl_encrypt_impl(data, method, password, options, iv,
&tag_out, aad, tag_length);
}
Variant HHVM_FUNCTION(openssl_decrypt, const String& data, const String& method,
const String& password,
int options /* = 0 */,
const String& iv /* = null_string */,
const String& tag /* = null_string */,
const String& aad /* = null_string */) {
const EVP_CIPHER *cipher_type = EVP_get_cipherbyname(method.c_str());
if (!cipher_type) {
raise_warning("Unknown cipher algorithm");
return false;
}
EVP_CIPHER_CTX* cipher_ctx = EVP_CIPHER_CTX_new();
if (!cipher_ctx) {
raise_warning("Failed to create cipher context");
return false;
}
SCOPE_EXIT {
EVP_CIPHER_CTX_free(cipher_ctx);
};
php_openssl_cipher_mode mode = php_openssl_load_cipher_mode(cipher_type);
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;
int outlen = decoded_data.size() + EVP_CIPHER_block_size(cipher_type);
String rv = String(outlen, ReserveString);
unsigned char *outbuf = (unsigned char*)rv.mutableData();
EVP_DecryptInit_ex(cipher_ctx, cipher_type, nullptr, nullptr, nullptr);
String new_iv;
// we do this after DecryptInit because validate_iv changes cipher_ctx for
// aead modes (must be initialized first).
if (!php_openssl_validate_iv(
std::move(iv),
EVP_CIPHER_iv_length(cipher_type),
new_iv,
cipher_ctx,
&mode)) {
return false;
}
// set the tag if required:
if (tag.size() > 0) {
if (!mode.is_aead) {
raise_warning(
"The tag is being ignored because the cipher method does not"
" support AEAD");
} else if (!EVP_CIPHER_CTX_ctrl(
cipher_ctx,
mode.aead_set_tag_flag,
tag.size(),
(unsigned char*)tag.data())) {
raise_warning("Setting tag for AEAD cipher decryption failed");
return false;
}
} else {
if (mode.is_aead) {
raise_warning("A tag should be provided when using AEAD mode");
return false;
}
}
if (password.size() > keylen) {
EVP_CIPHER_CTX_set_key_length(cipher_ctx, password.size());
}
EVP_DecryptInit_ex(
cipher_ctx,
nullptr,
nullptr,
(unsigned char*)key.data(),
(unsigned char*)new_iv.data());
if (options & k_OPENSSL_ZERO_PADDING) {
EVP_CIPHER_CTX_set_padding(cipher_ctx, 0);
}
// for single run aeads like CCM, we need to provide the length of the
// ciphertext before providing AAD or ciphertext.
if (mode.is_single_run_aead &&
!EVP_DecryptUpdate(
cipher_ctx, nullptr, &result_len, nullptr, decoded_data.size())) {
raise_warning("Setting of data length failed");
return false;
}
// set up aad:
if (mode.is_aead &&
!EVP_DecryptUpdate(
cipher_ctx,
nullptr,
&result_len,
(unsigned char*)aad.data(),
aad.size())) {
raise_warning("Setting of additional application data failed");
return false;
}
if (!EVP_DecryptUpdate(
cipher_ctx,
outbuf,
&result_len,
(unsigned char*)decoded_data.data(),
decoded_data.size())) {
return false;
}
outlen = result_len;
// if is_single_run_aead is enabled, DecryptFinal shouldn't be called.
// if something went wrong in this case, we would've caught it at
// DecryptUpdate.
if (mode.is_single_run_aead ||
EVP_DecryptFinal_ex(
cipher_ctx, (unsigned char*)outbuf + result_len, &result_len)) {
// don't want to do this if is_single_run_aead was enabled, since we didn't
// make a call to EVP_DecryptFinal.
if (!mode.is_single_run_aead) {
outlen += result_len;
}
rv.setSize(outlen);
return rv;
} else {
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_MD_CTX_new();
SCOPE_EXIT {
EVP_MD_CTX_free(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::CreateVec();
OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH,
aliases ? openssl_add_method_or_alias: openssl_add_method,
&ret);
return ret;
}
Variant HHVM_FUNCTION(openssl_get_curve_names) {
#ifdef HAVE_EVP_PKEY_EC
const size_t len = EC_get_builtin_curves(nullptr, 0);
std::unique_ptr<EC_builtin_curve[]> curves(new EC_builtin_curve[len]);
if (!EC_get_builtin_curves(curves.get(), len)) {
return false;
}
VecInit ret(len);
for (size_t i = 0; i < len; ++i) {
auto const sname = OBJ_nid2sn(curves[i].nid);
if (sname != nullptr) {
ret.append(String(sname, CopyString));
}
}
return ret.toArray();
#else
return false;
#endif
}
Array HHVM_FUNCTION(openssl_get_md_methods, bool aliases /* = false */) {
Array ret = Array::CreateVec();
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");
struct opensslExtension final : Extension {
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);
#ifdef HAVE_EVP_PKEY_EC
HHVM_RC_INT_SAME(OPENSSL_KEYTYPE_EC);
#endif
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_encrypt_with_tag);
HHVM_FE(openssl_decrypt);
HHVM_FE(openssl_digest);
HHVM_FE(openssl_get_cipher_methods);
HHVM_FE(openssl_get_curve_names);
HHVM_FE(openssl_get_md_methods);
loadSystemlib();
}
} s_openssl_extension;
///////////////////////////////////////////////////////////////////////////////
}