mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
2065 lines
56 KiB
C
2065 lines
56 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 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: |
|
|
| https://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. |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Jakub Zelenka <bukka@php.net> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "php_openssl_backend.h"
|
|
|
|
#include "zend_exceptions.h"
|
|
#include "ext/standard/md5.h" /* For make_digest_ex() */
|
|
#include "ext/standard/base64.h"
|
|
#ifdef PHP_WIN32
|
|
# include "win32/winutil.h"
|
|
#endif
|
|
|
|
/* Common */
|
|
#include <time.h>
|
|
|
|
#if (defined(PHP_WIN32) && defined(_MSC_VER))
|
|
#define timezone _timezone /* timezone is called _timezone in LibC */
|
|
#endif
|
|
|
|
|
|
/* openssl -> PHP "bridging" */
|
|
/* true global; readonly after module startup */
|
|
static char default_ssl_conf_filename[MAXPATHLEN];
|
|
|
|
void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname)
|
|
{
|
|
zval *data;
|
|
zval subitem, tmp;
|
|
int i;
|
|
char *sname;
|
|
int nid;
|
|
X509_NAME_ENTRY * ne;
|
|
ASN1_STRING * str = NULL;
|
|
ASN1_OBJECT * obj;
|
|
|
|
if (key != NULL) {
|
|
array_init(&subitem);
|
|
} else {
|
|
ZVAL_COPY_VALUE(&subitem, val);
|
|
}
|
|
|
|
for (i = 0; i < X509_NAME_entry_count(name); i++) {
|
|
const unsigned char *to_add = NULL;
|
|
int to_add_len = 0;
|
|
unsigned char *to_add_buf = NULL;
|
|
|
|
ne = X509_NAME_get_entry(name, i);
|
|
obj = X509_NAME_ENTRY_get_object(ne);
|
|
nid = OBJ_obj2nid(obj);
|
|
|
|
if (shortname) {
|
|
sname = (char *) OBJ_nid2sn(nid);
|
|
} else {
|
|
sname = (char *) OBJ_nid2ln(nid);
|
|
}
|
|
|
|
str = X509_NAME_ENTRY_get_data(ne);
|
|
if (ASN1_STRING_type(str) != V_ASN1_UTF8STRING) {
|
|
/* ASN1_STRING_to_UTF8(3): The converted data is copied into a newly allocated buffer */
|
|
to_add_len = ASN1_STRING_to_UTF8(&to_add_buf, str);
|
|
to_add = to_add_buf;
|
|
} else {
|
|
/* ASN1_STRING_get0_data(3): Since this is an internal pointer it should not be freed or modified in any way */
|
|
to_add = ASN1_STRING_get0_data(str);
|
|
to_add_len = ASN1_STRING_length(str);
|
|
}
|
|
|
|
if (to_add_len != -1) {
|
|
if ((data = zend_hash_str_find(Z_ARRVAL(subitem), sname, strlen(sname))) != NULL) {
|
|
if (Z_TYPE_P(data) == IS_ARRAY) {
|
|
add_next_index_stringl(data, (const char *)to_add, to_add_len);
|
|
} else if (Z_TYPE_P(data) == IS_STRING) {
|
|
array_init(&tmp);
|
|
add_next_index_str(&tmp, zend_string_copy(Z_STR_P(data)));
|
|
add_next_index_stringl(&tmp, (const char *)to_add, to_add_len);
|
|
zend_hash_str_update(Z_ARRVAL(subitem), sname, strlen(sname), &tmp);
|
|
}
|
|
} else {
|
|
/* it might be better to expand it and pass zval from ZVAL_STRING
|
|
* to zend_symtable_str_update so we do not silently drop const
|
|
* but we need a test to cover this part first */
|
|
add_assoc_stringl(&subitem, sname, (char *)to_add, to_add_len);
|
|
}
|
|
} else {
|
|
php_openssl_store_errors();
|
|
}
|
|
|
|
if (to_add_buf != NULL) {
|
|
OPENSSL_free(to_add_buf);
|
|
}
|
|
}
|
|
|
|
if (key != NULL) {
|
|
zend_hash_str_update(Z_ARRVAL_P(val), key, strlen(key), &subitem);
|
|
}
|
|
}
|
|
|
|
void php_openssl_add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str)
|
|
{
|
|
add_assoc_stringl(val, key, (char *)str->data, str->length);
|
|
}
|
|
|
|
time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr)
|
|
{
|
|
/*
|
|
* 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);
|
|
*/
|
|
|
|
time_t ret;
|
|
struct tm thetime;
|
|
char * strbuf;
|
|
char * thestr;
|
|
long gmadjust = 0;
|
|
size_t timestr_len;
|
|
|
|
if (ASN1_STRING_type(timestr) != V_ASN1_UTCTIME && ASN1_STRING_type(timestr) != V_ASN1_GENERALIZEDTIME) {
|
|
php_error_docref(NULL, E_WARNING, "Illegal ASN1 data type for timestamp");
|
|
return (time_t)-1;
|
|
}
|
|
|
|
timestr_len = (size_t)ASN1_STRING_length(timestr);
|
|
|
|
if (timestr_len != strlen((const char *)ASN1_STRING_get0_data(timestr))) {
|
|
php_error_docref(NULL, E_WARNING, "Illegal length in timestamp");
|
|
return (time_t)-1;
|
|
}
|
|
|
|
if (timestr_len < 13) {
|
|
php_error_docref(NULL, E_WARNING, "Unable to parse time string %s correctly", timestr->data);
|
|
return (time_t)-1;
|
|
}
|
|
|
|
if (ASN1_STRING_type(timestr) == V_ASN1_GENERALIZEDTIME && timestr_len < 15) {
|
|
php_error_docref(NULL, E_WARNING, "Unable to parse time string %s correctly", timestr->data);
|
|
return (time_t)-1;
|
|
}
|
|
|
|
strbuf = estrdup((const char *)ASN1_STRING_get0_data(timestr));
|
|
|
|
memset(&thetime, 0, sizeof(thetime));
|
|
|
|
/* we work backwards so that we can use atoi more easily */
|
|
|
|
thestr = strbuf + timestr_len - 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';
|
|
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;
|
|
ret = mktime(&thetime);
|
|
|
|
#ifdef HAVE_STRUCT_TM_TM_GMTOFF
|
|
gmadjust = thetime.tm_gmtoff;
|
|
#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
|
|
ret += gmadjust;
|
|
|
|
efree(strbuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int php_openssl_config_check_syntax(const char * section_label, const char * config_filename, const char * section, CONF *config)
|
|
{
|
|
X509V3_CTX ctx;
|
|
|
|
X509V3_set_ctx_test(&ctx);
|
|
X509V3_set_nconf(&ctx, config);
|
|
if (!X509V3_EXT_add_nconf(config, &ctx, (char *)section, NULL)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Error loading %s section %s of %s",
|
|
section_label,
|
|
section,
|
|
config_filename);
|
|
return FAILURE;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
char *php_openssl_conf_get_string(CONF *conf, const char *group, const char *name)
|
|
{
|
|
/* OpenSSL reports an error if a configuration value is not found.
|
|
* However, we don't want to generate errors for optional configuration. */
|
|
ERR_set_mark();
|
|
char *str = NCONF_get_string(conf, group, name);
|
|
ERR_pop_to_mark();
|
|
return str;
|
|
}
|
|
|
|
long php_openssl_conf_get_number(CONF *conf, const char *group, const char *name)
|
|
{
|
|
/* Same here, ignore errors. */
|
|
long res = 0;
|
|
ERR_set_mark();
|
|
NCONF_get_number(conf, group, name, &res);
|
|
ERR_pop_to_mark();
|
|
return res;
|
|
}
|
|
|
|
int php_openssl_add_oid_section(struct php_x509_request * req)
|
|
{
|
|
char * str;
|
|
STACK_OF(CONF_VALUE) * sktmp;
|
|
CONF_VALUE * cnf;
|
|
int i;
|
|
|
|
str = php_openssl_conf_get_string(req->req_config, NULL, "oid_section");
|
|
if (str == NULL) {
|
|
return SUCCESS;
|
|
}
|
|
sktmp = NCONF_get_section(req->req_config, str);
|
|
if (sktmp == NULL) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Problem loading oid section %s", str);
|
|
return FAILURE;
|
|
}
|
|
for (i = 0; i < sk_CONF_VALUE_num(sktmp); i++) {
|
|
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) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Problem creating object %s=%s", cnf->name, cnf->value);
|
|
return FAILURE;
|
|
}
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
int php_openssl_spki_cleanup(const char *src, char *dest)
|
|
{
|
|
int removed = 0;
|
|
|
|
while (*src) {
|
|
if (*src != '\n' && *src != '\r') {
|
|
*dest++ = *src;
|
|
} else {
|
|
++removed;
|
|
}
|
|
++src;
|
|
}
|
|
*dest = 0;
|
|
return removed;
|
|
}
|
|
|
|
|
|
int php_openssl_parse_config(struct php_x509_request * req, zval * optional_args)
|
|
{
|
|
char * str, path[MAXPATHLEN];
|
|
zval * item;
|
|
|
|
SET_OPTIONAL_STRING_ARG("config", req->config_filename, default_ssl_conf_filename);
|
|
SET_OPTIONAL_STRING_ARG("config_section_name", req->section_name, "req");
|
|
req->global_config = php_openssl_nconf_new();
|
|
if (!NCONF_load(req->global_config, default_ssl_conf_filename, NULL)) {
|
|
php_openssl_store_errors();
|
|
}
|
|
|
|
req->req_config = php_openssl_nconf_new();
|
|
if (!NCONF_load(req->req_config, req->config_filename, NULL)) {
|
|
return FAILURE;
|
|
}
|
|
|
|
/* read in the oids */
|
|
str = php_openssl_conf_get_string(req->req_config, NULL, "oid_file");
|
|
if (str != NULL && php_openssl_check_path_ex(str, strlen(str), path, 0, false, false, "oid_file")) {
|
|
BIO *oid_bio = BIO_new_file(path, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
|
|
if (oid_bio) {
|
|
OBJ_create_objects(oid_bio);
|
|
BIO_free(oid_bio);
|
|
php_openssl_store_errors();
|
|
}
|
|
}
|
|
if (php_openssl_add_oid_section(req) == FAILURE) {
|
|
return FAILURE;
|
|
}
|
|
SET_OPTIONAL_STRING_ARG("digest_alg", req->digest_name,
|
|
php_openssl_conf_get_string(req->req_config, req->section_name, "default_md"));
|
|
SET_OPTIONAL_STRING_ARG("x509_extensions", req->extensions_section,
|
|
php_openssl_conf_get_string(req->req_config, req->section_name, "x509_extensions"));
|
|
SET_OPTIONAL_STRING_ARG("req_extensions", req->request_extensions_section,
|
|
php_openssl_conf_get_string(req->req_config, req->section_name, "req_extensions"));
|
|
SET_OPTIONAL_LONG_ARG("private_key_bits", req->priv_key_bits,
|
|
php_openssl_conf_get_number(req->req_config, req->section_name, "default_bits"));
|
|
SET_OPTIONAL_LONG_ARG("private_key_type", req->priv_key_type, OPENSSL_KEYTYPE_DEFAULT);
|
|
|
|
if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "encrypt_key", sizeof("encrypt_key")-1)) != NULL) {
|
|
req->priv_key_encrypt = Z_TYPE_P(item) == IS_TRUE ? 1 : 0;
|
|
} else {
|
|
str = php_openssl_conf_get_string(req->req_config, req->section_name, "encrypt_rsa_key");
|
|
if (str == NULL) {
|
|
str = php_openssl_conf_get_string(req->req_config, req->section_name, "encrypt_key");
|
|
}
|
|
if (str != NULL && strcmp(str, "no") == 0) {
|
|
req->priv_key_encrypt = 0;
|
|
} else {
|
|
req->priv_key_encrypt = 1;
|
|
}
|
|
}
|
|
|
|
if (req->priv_key_encrypt &&
|
|
optional_args &&
|
|
(item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "encrypt_key_cipher", sizeof("encrypt_key_cipher")-1)) != NULL &&
|
|
Z_TYPE_P(item) == IS_LONG
|
|
) {
|
|
zend_long cipher_algo = Z_LVAL_P(item);
|
|
const EVP_CIPHER* cipher = php_openssl_get_evp_cipher_from_algo(cipher_algo);
|
|
if (cipher == NULL) {
|
|
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm for private key");
|
|
return FAILURE;
|
|
} else {
|
|
req->priv_key_encrypt_cipher = cipher;
|
|
}
|
|
} else {
|
|
req->priv_key_encrypt_cipher = NULL;
|
|
}
|
|
|
|
/* digest alg */
|
|
if (req->digest_name == NULL) {
|
|
req->digest_name = php_openssl_conf_get_string(req->req_config, req->section_name, "default_md");
|
|
}
|
|
if (req->digest_name != NULL) {
|
|
if (strcmp(req->digest_name, "null") == 0) {
|
|
req->digest = req->md_alg = EVP_md_null();
|
|
} else {
|
|
req->digest = req->md_alg = php_openssl_get_evp_md_by_name(req->digest_name);
|
|
}
|
|
}
|
|
if (req->md_alg == NULL) {
|
|
req->md_alg = req->digest = php_openssl_get_evp_md_by_name("sha1");
|
|
php_openssl_store_errors();
|
|
}
|
|
|
|
PHP_SSL_CONFIG_SYNTAX_CHECK(extensions_section);
|
|
#ifdef HAVE_EVP_PKEY_EC
|
|
/* set the ec group curve name */
|
|
req->curve_name = NID_undef;
|
|
if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "curve_name", sizeof("curve_name")-1)) != NULL
|
|
&& Z_TYPE_P(item) == IS_STRING) {
|
|
req->curve_name = OBJ_sn2nid(Z_STRVAL_P(item));
|
|
if (req->curve_name == NID_undef) {
|
|
php_error_docref(NULL, E_WARNING, "Unknown elliptic curve (short) name %s", Z_STRVAL_P(item));
|
|
return FAILURE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* set the string mask */
|
|
str = php_openssl_conf_get_string(req->req_config, req->section_name, "string_mask");
|
|
if (str != NULL && !ASN1_STRING_set_default_mask_asc(str)) {
|
|
php_error_docref(NULL, E_WARNING, "Invalid global string mask setting %s", str);
|
|
return FAILURE;
|
|
}
|
|
|
|
PHP_SSL_CONFIG_SYNTAX_CHECK(request_extensions_section);
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
void php_openssl_dispose_config(struct php_x509_request * req)
|
|
{
|
|
if (req->priv_key) {
|
|
EVP_PKEY_free(req->priv_key);
|
|
req->priv_key = NULL;
|
|
}
|
|
if (req->global_config) {
|
|
NCONF_free(req->global_config);
|
|
req->global_config = NULL;
|
|
}
|
|
if (req->req_config) {
|
|
NCONF_free(req->req_config);
|
|
req->req_config = NULL;
|
|
}
|
|
if (req->md_alg != NULL && req->md_alg != EVP_md_null()) {
|
|
php_openssl_release_evp_md(req->md_alg);
|
|
}
|
|
php_openssl_release_evp_cipher(req->priv_key_encrypt_cipher);
|
|
}
|
|
|
|
zend_result php_openssl_load_rand_file(const char * file, int *egdsocket, int *seeded)
|
|
{
|
|
char buffer[MAXPATHLEN];
|
|
|
|
*egdsocket = 0;
|
|
*seeded = 0;
|
|
|
|
if (file == NULL) {
|
|
file = RAND_file_name(buffer, sizeof(buffer));
|
|
#ifdef HAVE_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 SUCCESS;
|
|
#endif
|
|
}
|
|
if (file == NULL || RAND_load_file(file, -1) < 0) {
|
|
if (RAND_status() == 0) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Unable to load random state; not enough random data!");
|
|
return FAILURE;
|
|
}
|
|
return FAILURE;
|
|
}
|
|
*seeded = 1;
|
|
return SUCCESS;
|
|
}
|
|
|
|
zend_result php_openssl_write_rand_file(const char * file, int egdsocket, int seeded)
|
|
{
|
|
char buffer[MAXPATHLEN];
|
|
|
|
|
|
if (egdsocket || !seeded) {
|
|
/* if we did not manage to read the seed file, we should not write
|
|
* a low-entropy seed file back */
|
|
return FAILURE;
|
|
}
|
|
if (file == NULL) {
|
|
file = RAND_file_name(buffer, sizeof(buffer));
|
|
}
|
|
if (file == NULL || RAND_write_file(file) < 0) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Unable to write random state");
|
|
return FAILURE;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
void php_openssl_backend_init(void)
|
|
{
|
|
#ifdef LIBRESSL_VERSION_NUMBER
|
|
OPENSSL_config(NULL);
|
|
SSL_library_init();
|
|
OpenSSL_add_all_ciphers();
|
|
OpenSSL_add_all_digests();
|
|
OpenSSL_add_all_algorithms();
|
|
SSL_load_error_strings();
|
|
#else
|
|
#if PHP_OPENSSL_API_VERSION >= 0x30000 && defined(LOAD_OPENSSL_LEGACY_PROVIDER)
|
|
OSSL_PROVIDER_load(NULL, "legacy");
|
|
OSSL_PROVIDER_load(NULL, "default");
|
|
#endif
|
|
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL);
|
|
#endif
|
|
|
|
/* 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 {
|
|
strlcpy(default_ssl_conf_filename, config_filename, sizeof(default_ssl_conf_filename));
|
|
}
|
|
}
|
|
|
|
const char *php_openssl_get_conf_filename(void)
|
|
{
|
|
return default_ssl_conf_filename;
|
|
}
|
|
|
|
void php_openssl_set_cert_locations(zval *return_value)
|
|
{
|
|
add_assoc_string(return_value, "default_cert_file", (char *) X509_get_default_cert_file());
|
|
add_assoc_string(return_value, "default_cert_file_env", (char *) X509_get_default_cert_file_env());
|
|
add_assoc_string(return_value, "default_cert_dir", (char *) X509_get_default_cert_dir());
|
|
add_assoc_string(return_value, "default_cert_dir_env", (char *) X509_get_default_cert_dir_env());
|
|
add_assoc_string(return_value, "default_private_dir", (char *) X509_get_default_private_dir());
|
|
add_assoc_string(return_value, "default_default_cert_area", (char *) X509_get_default_cert_area());
|
|
add_assoc_string(return_value, "ini_cafile",
|
|
zend_ini_string("openssl.cafile", sizeof("openssl.cafile")-1, 0));
|
|
add_assoc_string(return_value, "ini_capath",
|
|
zend_ini_string("openssl.capath", sizeof("openssl.capath")-1, 0));
|
|
}
|
|
|
|
X509 *php_openssl_x509_from_str(
|
|
zend_string *cert_str, uint32_t arg_num, bool is_from_array, const char *option_name) {
|
|
X509 *cert = NULL;
|
|
char cert_path[MAXPATHLEN];
|
|
BIO *in;
|
|
|
|
if (ZSTR_LEN(cert_str) > 7 && memcmp(ZSTR_VAL(cert_str), "file://", sizeof("file://") - 1) == 0) {
|
|
if (!php_openssl_check_path_str_ex(cert_str, cert_path, arg_num, true, is_from_array, option_name)) {
|
|
return NULL;
|
|
}
|
|
|
|
in = BIO_new_file(cert_path, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
|
|
if (in == NULL) {
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
|
|
} else {
|
|
in = BIO_new_mem_buf(ZSTR_VAL(cert_str), (int) ZSTR_LEN(cert_str));
|
|
if (in == NULL) {
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
#ifdef TYPEDEF_D2I_OF
|
|
cert = (X509 *) PEM_ASN1_read_bio((d2i_of_void *)d2i_X509, PEM_STRING_X509, in, NULL, NULL, NULL);
|
|
#else
|
|
cert = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509, PEM_STRING_X509, in, NULL, NULL, NULL);
|
|
#endif
|
|
}
|
|
|
|
if (!BIO_free(in)) {
|
|
php_openssl_store_errors();
|
|
}
|
|
|
|
if (cert == NULL) {
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
|
|
return cert;
|
|
}
|
|
|
|
/* {{{ php_openssl_x509_from_param
|
|
Given a parameter, extract it into an X509 object.
|
|
The parameter can be:
|
|
. X509 object created using openssl_read_x509()
|
|
. a path to that cert if it starts with file://
|
|
. the cert data otherwise
|
|
*/
|
|
X509 *php_openssl_x509_from_param(
|
|
zend_object *cert_obj, zend_string *cert_str, uint32_t arg_num) {
|
|
if (cert_obj) {
|
|
return php_openssl_certificate_from_obj(cert_obj)->x509;
|
|
}
|
|
|
|
ZEND_ASSERT(cert_str);
|
|
|
|
return php_openssl_x509_from_str(cert_str, arg_num, false, NULL);
|
|
}
|
|
|
|
X509 *php_openssl_x509_from_zval(
|
|
zval *val, bool *free_cert, uint32_t arg_num, bool is_from_array, const char *option_name)
|
|
{
|
|
if (php_openssl_is_certificate_ce(val)) {
|
|
*free_cert = 0;
|
|
|
|
return php_openssl_certificate_from_obj(Z_OBJ_P(val))->x509;
|
|
}
|
|
|
|
*free_cert = 1;
|
|
|
|
zend_string *str = zval_try_get_string(val);
|
|
if (str == NULL) {
|
|
return NULL;
|
|
}
|
|
X509 *cert = php_openssl_x509_from_str(str, arg_num, is_from_array, option_name);
|
|
zend_string_release(str);
|
|
return cert;
|
|
}
|
|
|
|
zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool raw)
|
|
{
|
|
unsigned char md[EVP_MAX_MD_SIZE];
|
|
const EVP_MD *mdtype;
|
|
unsigned int n;
|
|
zend_string *ret;
|
|
|
|
if (!(mdtype = EVP_get_digestbyname(method))) {
|
|
php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
|
|
return NULL;
|
|
} else if (!X509_digest(peer, mdtype, md, &n)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_ERROR, "Could not generate signature");
|
|
return NULL;
|
|
}
|
|
|
|
if (raw) {
|
|
ret = zend_string_init((char*)md, n, 0);
|
|
} else {
|
|
ret = zend_string_alloc(n * 2, 0);
|
|
make_digest_ex(ZSTR_VAL(ret), md, n);
|
|
ZSTR_VAL(ret)[n * 2] = '\0';
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Special handling of subjectAltName, see CVE-2013-4073
|
|
* Christian Heimes
|
|
*/
|
|
int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension)
|
|
{
|
|
GENERAL_NAMES *names;
|
|
const X509V3_EXT_METHOD *method = NULL;
|
|
ASN1_OCTET_STRING *extension_data;
|
|
long i, length, num;
|
|
const unsigned char *p;
|
|
|
|
method = X509V3_EXT_get(extension);
|
|
if (method == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
extension_data = X509_EXTENSION_get_data(extension);
|
|
p = extension_data->data;
|
|
length = extension_data->length;
|
|
if (method->it) {
|
|
names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL, &p, length,
|
|
ASN1_ITEM_ptr(method->it)));
|
|
} else {
|
|
names = (GENERAL_NAMES*) (method->d2i(NULL, &p, length));
|
|
}
|
|
if (names == NULL) {
|
|
php_openssl_store_errors();
|
|
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_get0_data(as),
|
|
ASN1_STRING_length(as));
|
|
break;
|
|
case GEN_DNS:
|
|
BIO_puts(bio, "DNS:");
|
|
as = name->d.dNSName;
|
|
BIO_write(bio, ASN1_STRING_get0_data(as),
|
|
ASN1_STRING_length(as));
|
|
break;
|
|
case GEN_URI:
|
|
BIO_puts(bio, "URI:");
|
|
as = name->d.uniformResourceIdentifier;
|
|
BIO_write(bio, ASN1_STRING_get0_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;
|
|
}
|
|
|
|
STACK_OF(X509) *php_openssl_load_all_certs_from_file(
|
|
char *cert_file, size_t cert_file_len, uint32_t arg_num)
|
|
{
|
|
STACK_OF(X509_INFO) *sk=NULL;
|
|
STACK_OF(X509) *stack=NULL, *ret=NULL;
|
|
BIO *in=NULL;
|
|
X509_INFO *xi;
|
|
char cert_path[MAXPATHLEN];
|
|
|
|
if(!(stack = sk_X509_new_null())) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_ERROR, "Memory allocation failure");
|
|
goto end;
|
|
}
|
|
|
|
if (!php_openssl_check_path(cert_file, cert_file_len, cert_path, arg_num)) {
|
|
sk_X509_free(stack);
|
|
goto end;
|
|
}
|
|
|
|
if (!(in = BIO_new_file(cert_path, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)))) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Error opening the file, %s", cert_path);
|
|
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))) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Error reading the file, %s", cert_path);
|
|
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)) {
|
|
php_error_docref(NULL, E_WARNING, "No certificates in file, %s", cert_path);
|
|
sk_X509_free(stack);
|
|
goto end;
|
|
}
|
|
ret = stack;
|
|
end:
|
|
BIO_free(in);
|
|
sk_X509_INFO_free(sk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int php_openssl_check_cert(X509_STORE *ctx, X509 *x, STACK_OF(X509) *untrustedchain, int purpose)
|
|
{
|
|
int ret=0;
|
|
X509_STORE_CTX *csc;
|
|
|
|
csc = X509_STORE_CTX_new();
|
|
if (csc == NULL) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_ERROR, "Memory allocation failure");
|
|
return 0;
|
|
}
|
|
if (!X509_STORE_CTX_init(csc, ctx, x, untrustedchain)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Certificate store initialization failed");
|
|
return 0;
|
|
}
|
|
if (purpose >= 0 && !X509_STORE_CTX_set_purpose(csc, purpose)) {
|
|
php_openssl_store_errors();
|
|
}
|
|
ret = X509_verify_cert(csc);
|
|
if (ret < 0) {
|
|
php_openssl_store_errors();
|
|
}
|
|
X509_STORE_CTX_free(csc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* {{{ php_openssl_setup_verify
|
|
* calist is an array containing file and directory names. create a
|
|
* certificate store and add those certs to it for use in verification.
|
|
*/
|
|
X509_STORE *php_openssl_setup_verify(zval *calist, uint32_t arg_num)
|
|
{
|
|
X509_STORE *store;
|
|
X509_LOOKUP * dir_lookup, * file_lookup;
|
|
int ndirs = 0, nfiles = 0;
|
|
zval * item;
|
|
zend_stat_t sb = {0};
|
|
char file_path[MAXPATHLEN];
|
|
|
|
store = X509_STORE_new();
|
|
|
|
if (store == NULL) {
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
|
|
if (calist && (Z_TYPE_P(calist) == IS_ARRAY)) {
|
|
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(calist), item) {
|
|
zend_string *str = zval_try_get_string(item);
|
|
if (UNEXPECTED(!str)) {
|
|
X509_STORE_free(store);
|
|
return NULL;
|
|
}
|
|
|
|
if (!php_openssl_check_path_str_ex(str, file_path, arg_num, false, true, NULL)) {
|
|
zend_string_release(str);
|
|
continue;
|
|
}
|
|
zend_string_release(str);
|
|
|
|
if (VCWD_STAT(file_path, &sb) == -1) {
|
|
php_error_docref(NULL, E_WARNING, "Unable to stat %s", file_path);
|
|
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, file_path, X509_FILETYPE_PEM)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Error loading file %s", file_path);
|
|
} 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, file_path, X509_FILETYPE_PEM)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Error loading directory %s", file_path);
|
|
} else {
|
|
ndirs++;
|
|
}
|
|
dir_lookup = NULL;
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
}
|
|
if (nfiles == 0) {
|
|
file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
|
if (file_lookup == NULL || !X509_LOOKUP_load_file(file_lookup, NULL, X509_FILETYPE_DEFAULT)) {
|
|
php_openssl_store_errors();
|
|
}
|
|
}
|
|
if (ndirs == 0) {
|
|
dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
|
|
if (dir_lookup == NULL || !X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT)) {
|
|
php_openssl_store_errors();
|
|
}
|
|
}
|
|
return store;
|
|
}
|
|
|
|
|
|
/* Pop all X509 from Stack and free them, free the stack afterwards */
|
|
void php_openssl_sk_X509_free(STACK_OF(X509) * sk)
|
|
{
|
|
for (;;) {
|
|
X509* x = sk_X509_pop(sk);
|
|
if (!x) break;
|
|
X509_free(x);
|
|
}
|
|
sk_X509_free(sk);
|
|
}
|
|
|
|
STACK_OF(X509) *php_openssl_array_to_X509_sk(zval * zcerts, uint32_t arg_num, const char *option_name)
|
|
{
|
|
zval * zcertval;
|
|
STACK_OF(X509) * sk = NULL;
|
|
X509 * cert;
|
|
bool free_cert;
|
|
|
|
sk = sk_X509_new_null();
|
|
|
|
/* get certs */
|
|
if (Z_TYPE_P(zcerts) == IS_ARRAY) {
|
|
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zcerts), zcertval) {
|
|
cert = php_openssl_x509_from_zval(zcertval, &free_cert, arg_num, true, option_name);
|
|
if (cert == NULL) {
|
|
// TODO Add Warning?
|
|
goto clean_exit;
|
|
}
|
|
|
|
if (!free_cert) {
|
|
cert = X509_dup(cert);
|
|
|
|
if (cert == NULL) {
|
|
php_openssl_store_errors();
|
|
goto clean_exit;
|
|
}
|
|
|
|
}
|
|
sk_X509_push(sk, cert);
|
|
} ZEND_HASH_FOREACH_END();
|
|
} else {
|
|
/* a single certificate */
|
|
cert = php_openssl_x509_from_zval(zcerts, &free_cert, arg_num, false, option_name);
|
|
|
|
if (cert == NULL) {
|
|
// TODO Add Warning?
|
|
goto clean_exit;
|
|
}
|
|
|
|
if (!free_cert) {
|
|
cert = X509_dup(cert);
|
|
if (cert == NULL) {
|
|
php_openssl_store_errors();
|
|
goto clean_exit;
|
|
}
|
|
}
|
|
sk_X509_push(sk, cert);
|
|
}
|
|
|
|
clean_exit:
|
|
return sk;
|
|
}
|
|
|
|
zend_result php_openssl_csr_add_subj_entry(zval *item, X509_NAME *subj, int nid)
|
|
{
|
|
zend_string *str_item = zval_try_get_string(item);
|
|
if (UNEXPECTED(!str_item)) {
|
|
return FAILURE;
|
|
}
|
|
if (!X509_NAME_add_entry_by_NID(subj, nid, MBSTRING_UTF8,
|
|
(unsigned char*)ZSTR_VAL(str_item), -1, -1, 0))
|
|
{
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING,
|
|
"dn: add_entry_by_NID %d -> %s (failed; check error"
|
|
" queue and value of string_mask OpenSSL option "
|
|
"if illegal characters are reported)",
|
|
nid, ZSTR_VAL(str_item));
|
|
zend_string_release(str_item);
|
|
return FAILURE;
|
|
}
|
|
zend_string_release(str_item);
|
|
return SUCCESS;
|
|
}
|
|
|
|
zend_result php_openssl_csr_make(struct php_x509_request * req, X509_REQ * csr, zval * dn, zval * attribs)
|
|
{
|
|
STACK_OF(CONF_VALUE) * dn_sk, *attr_sk = NULL;
|
|
char * str, *dn_sect, *attr_sect;
|
|
|
|
dn_sect = NCONF_get_string(req->req_config, req->section_name, "distinguished_name");
|
|
if (dn_sect == NULL) {
|
|
php_openssl_store_errors();
|
|
return FAILURE;
|
|
}
|
|
dn_sk = NCONF_get_section(req->req_config, dn_sect);
|
|
if (dn_sk == NULL) {
|
|
php_openssl_store_errors();
|
|
return FAILURE;
|
|
}
|
|
attr_sect = php_openssl_conf_get_string(req->req_config, req->section_name, "attributes");
|
|
if (attr_sect == NULL) {
|
|
attr_sk = NULL;
|
|
} else {
|
|
attr_sk = NCONF_get_section(req->req_config, attr_sect);
|
|
if (attr_sk == NULL) {
|
|
php_openssl_store_errors();
|
|
return FAILURE;
|
|
}
|
|
}
|
|
/* setup the version number: version 1 */
|
|
if (X509_REQ_set_version(csr, 0L)) {
|
|
int i, nid;
|
|
char *type;
|
|
CONF_VALUE *v;
|
|
X509_NAME *subj;
|
|
zval *item, *subitem;
|
|
zend_string *strindex = NULL;
|
|
|
|
subj = X509_REQ_get_subject_name(csr);
|
|
/* apply values from the dn hash */
|
|
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(dn), strindex, item) {
|
|
if (strindex) {
|
|
int nid = OBJ_txt2nid(ZSTR_VAL(strindex));
|
|
if (nid != NID_undef) {
|
|
if (Z_TYPE_P(item) == IS_ARRAY) {
|
|
ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(item), i, subitem) {
|
|
if (php_openssl_csr_add_subj_entry(subitem, subj, nid) == FAILURE) {
|
|
return FAILURE;
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
} else if (php_openssl_csr_add_subj_entry(item, subj, nid) == FAILURE) {
|
|
return FAILURE;
|
|
}
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING, "dn: %s is not a recognized name", ZSTR_VAL(strindex));
|
|
}
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
/* Finally apply defaults from config file */
|
|
for(i = 0; i < sk_CONF_VALUE_num(dn_sk); i++) {
|
|
size_t len;
|
|
char buffer[200 + 1]; /*200 + \0 !*/
|
|
|
|
v = sk_CONF_VALUE_value(dn_sk, i);
|
|
type = v->name;
|
|
|
|
len = strlen(type);
|
|
if (len < sizeof("_default")) {
|
|
continue;
|
|
}
|
|
len -= sizeof("_default") - 1;
|
|
if (strcmp("_default", type + len) != 0) {
|
|
continue;
|
|
}
|
|
if (len > 200) {
|
|
len = 200;
|
|
}
|
|
memcpy(buffer, type, len);
|
|
buffer[len] = '\0';
|
|
type = buffer;
|
|
|
|
/* Skip past any leading X. X: X, etc to allow for multiple
|
|
* instances */
|
|
for (str = type; *str; str++) {
|
|
if (*str == ':' || *str == ',' || *str == '.') {
|
|
str++;
|
|
if (*str) {
|
|
type = str;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* if it is already set, skip this */
|
|
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_UTF8, (unsigned char*)v->value, -1, -1, 0)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "add_entry_by_txt %s -> %s (failed)", type, v->value);
|
|
return FAILURE;
|
|
}
|
|
if (!X509_NAME_entry_count(subj)) {
|
|
php_error_docref(NULL, E_WARNING, "No objects specified in config file");
|
|
return FAILURE;
|
|
}
|
|
}
|
|
if (attribs) {
|
|
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(attribs), strindex, item) {
|
|
int nid;
|
|
|
|
if (NULL == strindex) {
|
|
php_error_docref(NULL, E_WARNING, "attributes: numeric fild names are not supported");
|
|
continue;
|
|
}
|
|
|
|
nid = OBJ_txt2nid(ZSTR_VAL(strindex));
|
|
if (nid != NID_undef) {
|
|
zend_string *str_item = zval_try_get_string(item);
|
|
if (UNEXPECTED(!str_item)) {
|
|
return FAILURE;
|
|
}
|
|
if (!X509_REQ_add1_attr_by_NID(csr, nid, MBSTRING_UTF8, (unsigned char*)ZSTR_VAL(str_item), (int)ZSTR_LEN(str_item))) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "attributes: add_attr_by_NID %d -> %s (failed)", nid, ZSTR_VAL(str_item));
|
|
zend_string_release(str_item);
|
|
return FAILURE;
|
|
}
|
|
zend_string_release(str_item);
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING, "attributes: %s is not a recognized attribute name", ZSTR_VAL(strindex));
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
for (i = 0; i < sk_CONF_VALUE_num(attr_sk); i++) {
|
|
v = sk_CONF_VALUE_value(attr_sk, i);
|
|
/* if it is already set, skip this */
|
|
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_UTF8, (unsigned char*)v->value, -1)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING,
|
|
"add1_attr_by_txt %s -> %s (failed; check error queue "
|
|
"and value of string_mask OpenSSL option if illegal "
|
|
"characters are reported)",
|
|
v->name, v->value);
|
|
return FAILURE;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
php_openssl_store_errors();
|
|
}
|
|
|
|
if (!X509_REQ_set_pubkey(csr, req->priv_key)) {
|
|
php_openssl_store_errors();
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
X509_REQ *php_openssl_csr_from_str(zend_string *csr_str, uint32_t arg_num)
|
|
{
|
|
X509_REQ * csr = NULL;
|
|
char file_path[MAXPATHLEN];
|
|
BIO * in;
|
|
|
|
if (ZSTR_LEN(csr_str) > 7 && memcmp(ZSTR_VAL(csr_str), "file://", sizeof("file://") - 1) == 0) {
|
|
if (!php_openssl_check_path_str(csr_str, file_path, arg_num)) {
|
|
return NULL;
|
|
}
|
|
in = BIO_new_file(file_path, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
|
|
} else {
|
|
in = BIO_new_mem_buf(ZSTR_VAL(csr_str), (int) ZSTR_LEN(csr_str));
|
|
}
|
|
|
|
if (in == NULL) {
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
|
|
csr = PEM_read_bio_X509_REQ(in, NULL,NULL,NULL);
|
|
if (csr == NULL) {
|
|
php_openssl_store_errors();
|
|
}
|
|
|
|
BIO_free(in);
|
|
|
|
return csr;
|
|
}
|
|
|
|
X509_REQ *php_openssl_csr_from_param(
|
|
zend_object *csr_obj, zend_string *csr_str, uint32_t arg_num)
|
|
{
|
|
if (csr_obj) {
|
|
return php_openssl_request_from_obj(csr_obj)->csr;
|
|
}
|
|
|
|
ZEND_ASSERT(csr_str);
|
|
|
|
return php_openssl_csr_from_str(csr_str, arg_num);
|
|
}
|
|
|
|
EVP_PKEY *php_openssl_extract_public_key(EVP_PKEY *priv_key)
|
|
{
|
|
/* Extract public key portion by round-tripping through PEM. */
|
|
BIO *bio = BIO_new(BIO_s_mem());
|
|
if (!bio || !PEM_write_bio_PUBKEY(bio, priv_key)) {
|
|
BIO_free(bio);
|
|
return NULL;
|
|
}
|
|
|
|
EVP_PKEY *pub_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
|
|
BIO_free(bio);
|
|
return pub_key;
|
|
}
|
|
|
|
static int php_openssl_pem_password_cb(char *buf, int size, int rwflag, void *userdata)
|
|
{
|
|
struct php_openssl_pem_password *password = userdata;
|
|
|
|
if (password == NULL || password->key == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
size = (password->len > size) ? size : password->len;
|
|
memcpy(buf, password->key, size);
|
|
|
|
return size;
|
|
}
|
|
|
|
EVP_PKEY *php_openssl_pkey_from_zval(
|
|
zval *val, int public_key, char *passphrase, size_t passphrase_len, uint32_t arg_num)
|
|
{
|
|
EVP_PKEY *key = NULL;
|
|
X509 *cert = NULL;
|
|
bool free_cert = false, is_file = false;
|
|
char file_path[MAXPATHLEN];
|
|
zval tmp;
|
|
|
|
ZVAL_NULL(&tmp);
|
|
|
|
#define TMP_CLEAN \
|
|
if (Z_TYPE(tmp) == IS_STRING) {\
|
|
zval_ptr_dtor_str(&tmp); \
|
|
} \
|
|
return NULL;
|
|
|
|
if (Z_TYPE_P(val) == IS_ARRAY) {
|
|
zval * zphrase;
|
|
|
|
/* get passphrase */
|
|
|
|
if ((zphrase = zend_hash_index_find(Z_ARRVAL_P(val), 1)) == NULL) {
|
|
zend_value_error("Key array must be of the form array(0 => key, 1 => phrase)");
|
|
return NULL;
|
|
}
|
|
|
|
if (Z_TYPE_P(zphrase) == IS_STRING) {
|
|
passphrase = Z_STRVAL_P(zphrase);
|
|
passphrase_len = Z_STRLEN_P(zphrase);
|
|
} else {
|
|
ZVAL_COPY(&tmp, zphrase);
|
|
if (!try_convert_to_string(&tmp)) {
|
|
zval_ptr_dtor(&tmp);
|
|
return NULL;
|
|
}
|
|
|
|
passphrase = Z_STRVAL(tmp);
|
|
passphrase_len = Z_STRLEN(tmp);
|
|
}
|
|
|
|
/* now set val to be the key param and continue */
|
|
if ((val = zend_hash_index_find(Z_ARRVAL_P(val), 0)) == NULL) {
|
|
zend_value_error("Key array must be of the form array(0 => key, 1 => phrase)");
|
|
TMP_CLEAN;
|
|
}
|
|
}
|
|
|
|
if (php_openssl_is_pkey_ce(val)) {
|
|
php_openssl_pkey_object *obj = php_openssl_pkey_from_obj(Z_OBJ_P(val));
|
|
key = obj->pkey;
|
|
bool is_priv = obj->is_private;
|
|
|
|
/* check whether it is actually a private key if requested */
|
|
if (!public_key && !is_priv) {
|
|
php_error_docref(NULL, E_WARNING, "Supplied key param is a public key");
|
|
TMP_CLEAN;
|
|
}
|
|
|
|
if (public_key && is_priv) {
|
|
php_error_docref(NULL, E_WARNING, "Don't know how to get public key from this private key");
|
|
TMP_CLEAN;
|
|
} else {
|
|
if (Z_TYPE(tmp) == IS_STRING) {
|
|
zval_ptr_dtor_str(&tmp);
|
|
}
|
|
|
|
EVP_PKEY_up_ref(key);
|
|
return key;
|
|
}
|
|
} else if (php_openssl_is_certificate_ce(val)) {
|
|
cert = php_openssl_certificate_from_obj(Z_OBJ_P(val))->x509;
|
|
} else {
|
|
/* force it to be a string and check if it refers to a file */
|
|
/* passing non string values leaks, object uses toString, it returns NULL
|
|
* See bug38255.phpt
|
|
*/
|
|
if (!(Z_TYPE_P(val) == IS_STRING || Z_TYPE_P(val) == IS_OBJECT)) {
|
|
TMP_CLEAN;
|
|
}
|
|
zend_string *val_str = zval_try_get_string(val);
|
|
if (!val_str) {
|
|
TMP_CLEAN;
|
|
}
|
|
|
|
if (ZSTR_LEN(val_str) > 7 && memcmp(ZSTR_VAL(val_str), "file://", sizeof("file://") - 1) == 0) {
|
|
if (!php_openssl_check_path_str(val_str, file_path, arg_num)) {
|
|
zend_string_release_ex(val_str, false);
|
|
TMP_CLEAN;
|
|
}
|
|
is_file = true;
|
|
}
|
|
/* it's an X509 file/cert of some kind, and we need to extract the data from that */
|
|
if (public_key) {
|
|
php_openssl_errors_set_mark();
|
|
cert = php_openssl_x509_from_str(val_str, arg_num, false, NULL);
|
|
|
|
if (cert) {
|
|
free_cert = 1;
|
|
} else {
|
|
/* not a X509 certificate, try to retrieve public key */
|
|
php_openssl_errors_restore_mark();
|
|
BIO* in;
|
|
if (is_file) {
|
|
in = BIO_new_file(file_path, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
|
|
} else {
|
|
in = BIO_new_mem_buf(ZSTR_VAL(val_str), (int)ZSTR_LEN(val_str));
|
|
}
|
|
if (in == NULL) {
|
|
php_openssl_store_errors();
|
|
zend_string_release_ex(val_str, false);
|
|
TMP_CLEAN;
|
|
}
|
|
key = PEM_read_bio_PUBKEY(in, NULL,NULL, NULL);
|
|
BIO_free(in);
|
|
}
|
|
} else {
|
|
/* we want the private key */
|
|
BIO *in;
|
|
|
|
if (is_file) {
|
|
in = BIO_new_file(file_path, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY));
|
|
} else {
|
|
in = BIO_new_mem_buf(ZSTR_VAL(val_str), (int)ZSTR_LEN(val_str));
|
|
}
|
|
|
|
if (in == NULL) {
|
|
zend_string_release_ex(val_str, false);
|
|
TMP_CLEAN;
|
|
}
|
|
if (passphrase == NULL) {
|
|
key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
|
|
} else {
|
|
struct php_openssl_pem_password password;
|
|
password.key = passphrase;
|
|
password.len = passphrase_len;
|
|
key = PEM_read_bio_PrivateKey(in, NULL, php_openssl_pem_password_cb, &password);
|
|
}
|
|
BIO_free(in);
|
|
}
|
|
|
|
zend_string_release_ex(val_str, false);
|
|
}
|
|
|
|
if (key == NULL) {
|
|
php_openssl_store_errors();
|
|
|
|
if (public_key && cert) {
|
|
/* extract public key from X509 cert */
|
|
key = (EVP_PKEY *) X509_get_pubkey(cert);
|
|
if (key == NULL) {
|
|
php_openssl_store_errors();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (free_cert) {
|
|
X509_free(cert);
|
|
}
|
|
|
|
if (Z_TYPE(tmp) == IS_STRING) {
|
|
zval_ptr_dtor_str(&tmp);
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, size_t key_size)
|
|
{
|
|
EVP_PKEY_CTX *ctx = php_openssl_pkey_new_from_pkey(key);
|
|
if (!ctx) {
|
|
return NULL;
|
|
}
|
|
|
|
if (EVP_PKEY_derive_init(ctx) <= 0 ||
|
|
EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0 ||
|
|
(key_size == 0 && EVP_PKEY_derive(ctx, NULL, &key_size) <= 0)) {
|
|
php_openssl_store_errors();
|
|
EVP_PKEY_CTX_free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
zend_string *result = zend_string_alloc(key_size, 0);
|
|
if (EVP_PKEY_derive(ctx, (unsigned char *)ZSTR_VAL(result), &key_size) <= 0) {
|
|
php_openssl_store_errors();
|
|
zend_string_release_ex(result, 0);
|
|
EVP_PKEY_CTX_free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
ZSTR_LEN(result) = key_size;
|
|
ZSTR_VAL(result)[key_size] = 0;
|
|
EVP_PKEY_CTX_free(ctx);
|
|
return result;
|
|
}
|
|
|
|
static int php_openssl_get_evp_pkey_type(int key_type) {
|
|
switch (key_type) {
|
|
case OPENSSL_KEYTYPE_RSA:
|
|
return EVP_PKEY_RSA;
|
|
#if !defined(OPENSSL_NO_DSA)
|
|
case OPENSSL_KEYTYPE_DSA:
|
|
return EVP_PKEY_DSA;
|
|
#endif
|
|
#if !defined(NO_DH)
|
|
case OPENSSL_KEYTYPE_DH:
|
|
return EVP_PKEY_DH;
|
|
#endif
|
|
#ifdef HAVE_EVP_PKEY_EC
|
|
case OPENSSL_KEYTYPE_EC:
|
|
return EVP_PKEY_EC;
|
|
#endif
|
|
#if PHP_OPENSSL_API_VERSION >= 0x30000
|
|
case OPENSSL_KEYTYPE_X25519:
|
|
return EVP_PKEY_X25519;
|
|
case OPENSSL_KEYTYPE_ED25519:
|
|
return EVP_PKEY_ED25519;
|
|
case OPENSSL_KEYTYPE_X448:
|
|
return EVP_PKEY_X448;
|
|
case OPENSSL_KEYTYPE_ED448:
|
|
return EVP_PKEY_ED448;
|
|
#endif
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static const char *php_openssl_get_evp_pkey_name(int key_type) {
|
|
switch (key_type) {
|
|
case OPENSSL_KEYTYPE_RSA:
|
|
return "RSA";
|
|
#if !defined(OPENSSL_NO_DSA)
|
|
case OPENSSL_KEYTYPE_DSA:
|
|
return "DSA";
|
|
#endif
|
|
#if !defined(NO_DH)
|
|
case OPENSSL_KEYTYPE_DH:
|
|
return "DH";
|
|
#endif
|
|
#ifdef HAVE_EVP_PKEY_EC
|
|
case OPENSSL_KEYTYPE_EC:
|
|
return "EC";
|
|
#endif
|
|
#if PHP_OPENSSL_API_VERSION >= 0x30000
|
|
case OPENSSL_KEYTYPE_X25519:
|
|
return "X25519";
|
|
case OPENSSL_KEYTYPE_ED25519:
|
|
return "ED25519";
|
|
case OPENSSL_KEYTYPE_X448:
|
|
return "X448";
|
|
case OPENSSL_KEYTYPE_ED448:
|
|
return "ED448";
|
|
#endif
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
EVP_PKEY *php_openssl_generate_private_key(struct php_x509_request * req)
|
|
{
|
|
if (req->priv_key_bits < MIN_KEY_LENGTH) {
|
|
php_error_docref(NULL, E_WARNING, "Private key length must be at least %d bits, configured to %d",
|
|
MIN_KEY_LENGTH, req->priv_key_bits);
|
|
return NULL;
|
|
}
|
|
|
|
int type = php_openssl_get_evp_pkey_type(req->priv_key_type);
|
|
if (type < 0) {
|
|
php_error_docref(NULL, E_WARNING, "Unsupported private key type");
|
|
return NULL;
|
|
}
|
|
const char *name = php_openssl_get_evp_pkey_name(req->priv_key_type);
|
|
|
|
int egdsocket, seeded;
|
|
char *randfile = php_openssl_conf_get_string(req->req_config, req->section_name, "RANDFILE");
|
|
php_openssl_load_rand_file(randfile, &egdsocket, &seeded);
|
|
|
|
EVP_PKEY *key = NULL;
|
|
EVP_PKEY *params = NULL;
|
|
EVP_PKEY_CTX *ctx = php_openssl_pkey_new_from_name(name, type);
|
|
if (!ctx) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (type != EVP_PKEY_RSA) {
|
|
if (EVP_PKEY_paramgen_init(ctx) <= 0) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
|
|
switch (type) {
|
|
#if !defined(OPENSSL_NO_DSA)
|
|
case EVP_PKEY_DSA:
|
|
if (EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, req->priv_key_bits) <= 0) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
#endif
|
|
#if !defined(NO_DH)
|
|
case EVP_PKEY_DH:
|
|
if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, req->priv_key_bits) <= 0) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_EVP_PKEY_EC
|
|
case EVP_PKEY_EC:
|
|
if (req->curve_name == NID_undef) {
|
|
php_error_docref(NULL, E_WARNING, "Missing configuration value: \"curve_name\" not set");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, req->curve_name) <= 0 ||
|
|
EVP_PKEY_CTX_set_ec_param_enc(ctx, OPENSSL_EC_NAMED_CURVE) <= 0) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
#endif
|
|
#if PHP_OPENSSL_API_VERSION >= 0x30000
|
|
case EVP_PKEY_X25519:
|
|
break;
|
|
case EVP_PKEY_ED25519:
|
|
break;
|
|
case EVP_PKEY_X448:
|
|
break;
|
|
case EVP_PKEY_ED448:
|
|
break;
|
|
#endif
|
|
EMPTY_SWITCH_DEFAULT_CASE()
|
|
}
|
|
|
|
if (EVP_PKEY_paramgen(ctx, ¶ms) <= 0) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
|
|
EVP_PKEY_CTX_free(ctx);
|
|
ctx = php_openssl_pkey_new_from_pkey(params);
|
|
if (!ctx) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (EVP_PKEY_keygen_init(ctx) <= 0) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (type == EVP_PKEY_RSA && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, req->priv_key_bits) <= 0) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (EVP_PKEY_keygen(ctx, &key) <= 0) {
|
|
php_openssl_store_errors();
|
|
goto cleanup;
|
|
}
|
|
|
|
req->priv_key = key;
|
|
|
|
cleanup:
|
|
php_openssl_write_rand_file(randfile, egdsocket, seeded);
|
|
EVP_PKEY_free(params);
|
|
EVP_PKEY_CTX_free(ctx);
|
|
return key;
|
|
}
|
|
|
|
void php_openssl_add_bn_to_array(zval *ary, const BIGNUM *bn, const char *name) {
|
|
if (bn != NULL) {
|
|
int len = BN_num_bytes(bn);
|
|
zend_string *str = zend_string_alloc(len, 0);
|
|
BN_bn2bin(bn, (unsigned char *)ZSTR_VAL(str));
|
|
ZSTR_VAL(str)[len] = 0;
|
|
add_assoc_str(ary, name, str);
|
|
}
|
|
}
|
|
|
|
BIGNUM *php_openssl_dh_pub_from_priv(BIGNUM *priv_key, BIGNUM *g, BIGNUM *p)
|
|
{
|
|
BIGNUM *pub_key, *priv_key_const_time;
|
|
BN_CTX *ctx;
|
|
|
|
pub_key = BN_new();
|
|
if (pub_key == NULL) {
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
|
|
priv_key_const_time = BN_new();
|
|
if (priv_key_const_time == NULL) {
|
|
BN_free(pub_key);
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
ctx = BN_CTX_new();
|
|
if (ctx == NULL) {
|
|
BN_free(pub_key);
|
|
BN_free(priv_key_const_time);
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
|
|
BN_with_flags(priv_key_const_time, priv_key, BN_FLG_CONSTTIME);
|
|
|
|
if (!BN_mod_exp_mont(pub_key, g, priv_key_const_time, p, ctx, NULL)) {
|
|
BN_free(pub_key);
|
|
php_openssl_store_errors();
|
|
pub_key = NULL;
|
|
}
|
|
|
|
BN_free(priv_key_const_time);
|
|
BN_CTX_free(ctx);
|
|
|
|
return pub_key;
|
|
}
|
|
|
|
BIO *php_openssl_bio_new_file(
|
|
const char *filename, size_t filename_len, uint32_t arg_num, const char *mode)
|
|
{
|
|
char file_path[MAXPATHLEN];
|
|
BIO *bio;
|
|
|
|
if (!php_openssl_check_path(filename, filename_len, file_path, arg_num)) {
|
|
return NULL;
|
|
}
|
|
|
|
bio = BIO_new_file(file_path, mode);
|
|
if (bio == NULL) {
|
|
php_openssl_store_errors();
|
|
return NULL;
|
|
}
|
|
|
|
return bio;
|
|
}
|
|
|
|
void php_openssl_add_method_or_alias(const OBJ_NAME *name, void *arg)
|
|
{
|
|
add_next_index_string((zval*)arg, (char*)name->name);
|
|
}
|
|
|
|
void php_openssl_add_method(const OBJ_NAME *name, void *arg)
|
|
{
|
|
if (name->alias == 0) {
|
|
add_next_index_string((zval*)arg, (char*)name->name);
|
|
}
|
|
}
|
|
|
|
void php_openssl_get_md_methods(zval *return_value, bool aliases)
|
|
{
|
|
array_init(return_value);
|
|
OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_MD_METH,
|
|
aliases ? php_openssl_add_method_or_alias: php_openssl_add_method,
|
|
return_value);
|
|
}
|
|
|
|
void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, const EVP_CIPHER *cipher_type)
|
|
{
|
|
int cipher_mode = EVP_CIPHER_mode(cipher_type);
|
|
memset(mode, 0, sizeof(struct php_openssl_cipher_mode));
|
|
switch (cipher_mode) {
|
|
case EVP_CIPH_GCM_MODE:
|
|
case EVP_CIPH_CCM_MODE:
|
|
/* We check for EVP_CIPH_OCB_MODE, because LibreSSL does not support it. */
|
|
#ifdef EVP_CIPH_OCB_MODE
|
|
case EVP_CIPH_OCB_MODE:
|
|
/* For OCB mode, explicitly set the tag length even when decrypting,
|
|
* see https://github.com/openssl/openssl/issues/8331. */
|
|
mode->set_tag_length_always = cipher_mode == EVP_CIPH_OCB_MODE;
|
|
#endif
|
|
php_openssl_set_aead_flags(mode);
|
|
mode->set_tag_length_when_encrypting = cipher_mode == EVP_CIPH_CCM_MODE;
|
|
mode->is_single_run_aead = cipher_mode == EVP_CIPH_CCM_MODE;
|
|
break;
|
|
#ifdef NID_chacha20_poly1305
|
|
default:
|
|
if (EVP_CIPHER_nid(cipher_type) == NID_chacha20_poly1305) {
|
|
php_openssl_set_aead_flags(mode);
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
zend_result php_openssl_validate_iv(const char **piv, size_t *piv_len, size_t iv_required_len,
|
|
bool *free_iv, EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode)
|
|
{
|
|
char *iv_new;
|
|
|
|
if (mode->is_aead) {
|
|
if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) <= 0) {
|
|
php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed");
|
|
return FAILURE;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
/* Best case scenario, user behaved */
|
|
if (*piv_len == iv_required_len) {
|
|
return SUCCESS;
|
|
}
|
|
|
|
iv_new = ecalloc(1, iv_required_len + 1);
|
|
|
|
if (*piv_len == 0) {
|
|
/* BC behavior */
|
|
*piv_len = iv_required_len;
|
|
*piv = iv_new;
|
|
*free_iv = 1;
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
if (*piv_len < iv_required_len) {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"IV passed is only %zd bytes long, cipher expects an IV of precisely %zd bytes, padding with \\0",
|
|
*piv_len, iv_required_len);
|
|
memcpy(iv_new, *piv, *piv_len);
|
|
*piv_len = iv_required_len;
|
|
*piv = iv_new;
|
|
*free_iv = 1;
|
|
return SUCCESS;
|
|
}
|
|
|
|
php_error_docref(NULL, E_WARNING,
|
|
"IV passed is %zd bytes long which is longer than the %zd expected by selected cipher, truncating",
|
|
*piv_len, iv_required_len);
|
|
memcpy(iv_new, *piv, iv_required_len);
|
|
*piv_len = iv_required_len;
|
|
*piv = iv_new;
|
|
*free_iv = 1;
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
zend_result php_openssl_cipher_init(const EVP_CIPHER *cipher_type,
|
|
EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode,
|
|
const char **ppassword, size_t *ppassword_len, bool *free_password,
|
|
const char **piv, size_t *piv_len, bool *free_iv,
|
|
const char *tag, int tag_len, zend_long options, int enc)
|
|
{
|
|
unsigned char *key;
|
|
int key_len, password_len;
|
|
size_t max_iv_len;
|
|
|
|
*free_password = 0;
|
|
|
|
max_iv_len = EVP_CIPHER_iv_length(cipher_type);
|
|
if (enc && *piv_len == 0 && max_iv_len > 0 && !mode->is_aead) {
|
|
php_error_docref(NULL, E_WARNING,
|
|
"Using an empty Initialization Vector (iv) is potentially insecure and not recommended");
|
|
}
|
|
|
|
if (!EVP_CipherInit_ex(cipher_ctx, cipher_type, NULL, NULL, NULL, enc)) {
|
|
php_openssl_store_errors();
|
|
return FAILURE;
|
|
}
|
|
if (php_openssl_validate_iv(piv, piv_len, max_iv_len, free_iv, cipher_ctx, mode) == FAILURE) {
|
|
return FAILURE;
|
|
}
|
|
if (mode->set_tag_length_always || (enc && mode->set_tag_length_when_encrypting)) {
|
|
if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL) <= 0) {
|
|
php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed");
|
|
return FAILURE;
|
|
}
|
|
}
|
|
if (!enc && tag && tag_len > 0) {
|
|
if (!mode->is_aead) {
|
|
php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher algorithm does not support AEAD");
|
|
} else if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag) <= 0) {
|
|
php_error_docref(NULL, E_WARNING, "Setting tag for AEAD cipher decryption failed");
|
|
return FAILURE;
|
|
}
|
|
}
|
|
/* check and set key */
|
|
password_len = (int) *ppassword_len;
|
|
key_len = EVP_CIPHER_key_length(cipher_type);
|
|
if (key_len > password_len) {
|
|
if ((OPENSSL_DONT_ZERO_PAD_KEY & options) && !EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Key length cannot be set for the cipher algorithm");
|
|
return FAILURE;
|
|
}
|
|
key = emalloc(key_len);
|
|
memset(key, 0, key_len);
|
|
memcpy(key, *ppassword, password_len);
|
|
*ppassword = (char *) key;
|
|
*ppassword_len = key_len;
|
|
*free_password = 1;
|
|
} else {
|
|
if (password_len > key_len && !EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len)) {
|
|
php_openssl_store_errors();
|
|
}
|
|
key = (unsigned char*)*ppassword;
|
|
}
|
|
|
|
if (!EVP_CipherInit_ex(cipher_ctx, NULL, NULL, key, (unsigned char *)*piv, enc)) {
|
|
php_openssl_store_errors();
|
|
return FAILURE;
|
|
}
|
|
if (options & OPENSSL_ZERO_PADDING) {
|
|
EVP_CIPHER_CTX_set_padding(cipher_ctx, 0);
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
zend_result php_openssl_cipher_update(const EVP_CIPHER *cipher_type,
|
|
EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode,
|
|
zend_string **poutbuf, int *poutlen, const char *data, size_t data_len,
|
|
const char *aad, size_t aad_len, int enc)
|
|
{
|
|
int i = 0;
|
|
|
|
if (mode->is_single_run_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, NULL, (int)data_len)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Setting of data length failed");
|
|
return FAILURE;
|
|
}
|
|
|
|
if (mode->is_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, (const unsigned char *) aad, (int) aad_len)) {
|
|
php_openssl_store_errors();
|
|
php_error_docref(NULL, E_WARNING, "Setting of additional application data failed");
|
|
return FAILURE;
|
|
}
|
|
|
|
*poutbuf = zend_string_alloc((int)data_len + EVP_CIPHER_block_size(cipher_type), 0);
|
|
|
|
if (!EVP_CipherUpdate(cipher_ctx, (unsigned char*)ZSTR_VAL(*poutbuf),
|
|
&i, (const unsigned char *)data, (int)data_len)) {
|
|
/* we don't show warning when we fail but if we ever do, then it should look like this:
|
|
if (mode->is_single_run_aead && !enc) {
|
|
php_error_docref(NULL, E_WARNING, "Tag verifycation failed");
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING, enc ? "Encryption failed" : "Decryption failed");
|
|
}
|
|
*/
|
|
php_openssl_store_errors();
|
|
zend_string_release_ex(*poutbuf, 0);
|
|
return FAILURE;
|
|
}
|
|
|
|
*poutlen = i;
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
PHP_OPENSSL_API zend_string* php_openssl_encrypt(
|
|
const char *data, size_t data_len,
|
|
const char *method, size_t method_len,
|
|
const char *password, size_t password_len,
|
|
zend_long options,
|
|
const char *iv, size_t iv_len,
|
|
zval *tag, zend_long tag_len,
|
|
const char *aad, size_t aad_len)
|
|
{
|
|
const EVP_CIPHER *cipher_type;
|
|
EVP_CIPHER_CTX *cipher_ctx;
|
|
struct php_openssl_cipher_mode mode;
|
|
int i = 0, outlen;
|
|
bool free_iv = 0, free_password = 0;
|
|
zend_string *outbuf = NULL;
|
|
|
|
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(data_len, data);
|
|
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(password_len, password);
|
|
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(aad_len, aad);
|
|
PHP_OPENSSL_CHECK_LONG_TO_INT_NULL_RETURN(tag_len, tag_len);
|
|
|
|
|
|
cipher_type = php_openssl_get_evp_cipher_by_name(method);
|
|
if (!cipher_type) {
|
|
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
|
|
return NULL;
|
|
}
|
|
|
|
cipher_ctx = EVP_CIPHER_CTX_new();
|
|
if (!cipher_ctx) {
|
|
php_openssl_release_evp_cipher(cipher_type);
|
|
php_error_docref(NULL, E_WARNING, "Failed to create cipher context");
|
|
return NULL;
|
|
}
|
|
|
|
php_openssl_load_cipher_mode(&mode, cipher_type);
|
|
|
|
if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode,
|
|
&password, &password_len, &free_password,
|
|
&iv, &iv_len, &free_iv, NULL, tag_len, options, 1) == FAILURE ||
|
|
php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen,
|
|
data, data_len, aad, aad_len, 1) == FAILURE) {
|
|
outbuf = NULL;
|
|
} else if (EVP_EncryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) {
|
|
outlen += i;
|
|
if (options & OPENSSL_RAW_DATA) {
|
|
ZSTR_VAL(outbuf)[outlen] = '\0';
|
|
ZSTR_LEN(outbuf) = outlen;
|
|
} else {
|
|
zend_string *base64_str;
|
|
|
|
base64_str = php_base64_encode((unsigned char*)ZSTR_VAL(outbuf), outlen);
|
|
zend_string_release_ex(outbuf, 0);
|
|
outbuf = base64_str;
|
|
}
|
|
if (mode.is_aead && tag) {
|
|
zend_string *tag_str = zend_string_alloc(tag_len, 0);
|
|
|
|
if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) > 0) {
|
|
ZSTR_VAL(tag_str)[tag_len] = '\0';
|
|
ZSTR_LEN(tag_str) = tag_len;
|
|
ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str);
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING, "Retrieving verification tag failed");
|
|
zend_string_release_ex(tag_str, 0);
|
|
zend_string_release_ex(outbuf, 0);
|
|
outbuf = NULL;
|
|
}
|
|
} else if (tag) {
|
|
ZEND_TRY_ASSIGN_REF_NULL(tag);
|
|
} else if (mode.is_aead) {
|
|
php_error_docref(NULL, E_WARNING, "A tag should be provided when using AEAD mode");
|
|
zend_string_release_ex(outbuf, 0);
|
|
outbuf = NULL;
|
|
}
|
|
} else {
|
|
php_openssl_store_errors();
|
|
zend_string_release_ex(outbuf, 0);
|
|
outbuf = NULL;
|
|
}
|
|
|
|
if (free_password) {
|
|
efree((void *) password);
|
|
}
|
|
if (free_iv) {
|
|
efree((void *) iv);
|
|
}
|
|
EVP_CIPHER_CTX_reset(cipher_ctx);
|
|
EVP_CIPHER_CTX_free(cipher_ctx);
|
|
php_openssl_release_evp_cipher(cipher_type);
|
|
return outbuf;
|
|
}
|
|
|
|
PHP_OPENSSL_API zend_string* php_openssl_decrypt(
|
|
const char *data, size_t data_len,
|
|
const char *method, size_t method_len,
|
|
const char *password, size_t password_len,
|
|
zend_long options,
|
|
const char *iv, size_t iv_len,
|
|
const char *tag, zend_long tag_len,
|
|
const char *aad, size_t aad_len)
|
|
{
|
|
const EVP_CIPHER *cipher_type;
|
|
EVP_CIPHER_CTX *cipher_ctx;
|
|
struct php_openssl_cipher_mode mode;
|
|
int i = 0, outlen;
|
|
zend_string *base64_str = NULL;
|
|
bool free_iv = 0, free_password = 0;
|
|
zend_string *outbuf = NULL;
|
|
|
|
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(data_len, data);
|
|
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(password_len, password);
|
|
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(aad_len, aad);
|
|
PHP_OPENSSL_CHECK_SIZE_T_TO_INT_NULL_RETURN(tag_len, tag);
|
|
|
|
|
|
cipher_type = php_openssl_get_evp_cipher_by_name(method);
|
|
if (!cipher_type) {
|
|
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
|
|
return NULL;
|
|
}
|
|
|
|
cipher_ctx = EVP_CIPHER_CTX_new();
|
|
if (!cipher_ctx) {
|
|
php_openssl_release_evp_cipher(cipher_type);
|
|
php_error_docref(NULL, E_WARNING, "Failed to create cipher context");
|
|
return NULL;
|
|
}
|
|
|
|
php_openssl_load_cipher_mode(&mode, cipher_type);
|
|
|
|
if (!(options & OPENSSL_RAW_DATA)) {
|
|
base64_str = php_base64_decode((unsigned char*)data, data_len);
|
|
if (!base64_str) {
|
|
php_error_docref(NULL, E_WARNING, "Failed to base64 decode the input");
|
|
EVP_CIPHER_CTX_free(cipher_ctx);
|
|
return NULL;
|
|
}
|
|
data_len = ZSTR_LEN(base64_str);
|
|
data = ZSTR_VAL(base64_str);
|
|
}
|
|
|
|
if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode,
|
|
&password, &password_len, &free_password,
|
|
&iv, &iv_len, &free_iv, tag, tag_len, options, 0) == FAILURE ||
|
|
php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen,
|
|
data, data_len, aad, aad_len, 0) == FAILURE) {
|
|
outbuf = NULL;
|
|
} else if (mode.is_single_run_aead ||
|
|
EVP_DecryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) {
|
|
outlen += i;
|
|
ZSTR_VAL(outbuf)[outlen] = '\0';
|
|
ZSTR_LEN(outbuf) = outlen;
|
|
} else {
|
|
php_openssl_store_errors();
|
|
zend_string_release_ex(outbuf, 0);
|
|
outbuf = NULL;
|
|
}
|
|
|
|
if (free_password) {
|
|
efree((void *) password);
|
|
}
|
|
if (free_iv) {
|
|
efree((void *) iv);
|
|
}
|
|
if (base64_str) {
|
|
zend_string_release_ex(base64_str, 0);
|
|
}
|
|
EVP_CIPHER_CTX_reset(cipher_ctx);
|
|
EVP_CIPHER_CTX_free(cipher_ctx);
|
|
php_openssl_release_evp_cipher(cipher_type);
|
|
return outbuf;
|
|
}
|
|
|
|
const EVP_CIPHER *php_openssl_get_evp_cipher_by_name_with_warning(const char *method)
|
|
{
|
|
const EVP_CIPHER *cipher_type;
|
|
|
|
cipher_type = php_openssl_get_evp_cipher_by_name(method);
|
|
if (!cipher_type) {
|
|
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
|
|
return NULL;
|
|
}
|
|
|
|
return cipher_type;
|
|
}
|
|
|
|
|
|
PHP_OPENSSL_API zend_long php_openssl_cipher_iv_length(const char *method)
|
|
{
|
|
const EVP_CIPHER *cipher_type = php_openssl_get_evp_cipher_by_name_with_warning(method);
|
|
if (cipher_type == NULL) {
|
|
return -1;
|
|
}
|
|
int iv_length = EVP_CIPHER_iv_length(cipher_type);
|
|
php_openssl_release_evp_cipher(cipher_type);
|
|
|
|
return iv_length;
|
|
}
|
|
|
|
PHP_OPENSSL_API zend_long php_openssl_cipher_key_length(const char *method)
|
|
{
|
|
const EVP_CIPHER *cipher_type = php_openssl_get_evp_cipher_by_name_with_warning(method);
|
|
if (cipher_type == NULL) {
|
|
return -1;
|
|
}
|
|
int key_length = EVP_CIPHER_key_length(cipher_type);
|
|
php_openssl_release_evp_cipher(cipher_type);
|
|
|
|
return key_length;
|
|
}
|
|
|
|
PHP_OPENSSL_API zend_string* php_openssl_random_pseudo_bytes(zend_long buffer_length)
|
|
{
|
|
zend_string *buffer = NULL;
|
|
if (buffer_length <= 0) {
|
|
zend_argument_value_error(1, "must be greater than 0");
|
|
return NULL;
|
|
}
|
|
if (ZEND_LONG_INT_OVFL(buffer_length)) {
|
|
zend_argument_value_error(1, "must be less than or equal to %d", INT_MAX);
|
|
return NULL;
|
|
}
|
|
buffer = zend_string_alloc(buffer_length, 0);
|
|
|
|
PHP_OPENSSL_CHECK_LONG_TO_INT_NULL_RETURN(buffer_length, length);
|
|
if (RAND_bytes((unsigned char*)ZSTR_VAL(buffer), (int)buffer_length) <= 0) {
|
|
php_openssl_store_errors();
|
|
zend_string_release_ex(buffer, 0);
|
|
zend_throw_exception(zend_ce_exception, "Error reading from source device", 0);
|
|
return NULL;
|
|
}
|
|
|
|
return buffer;
|
|
}
|