#include <openssl/crypto.h>
#include <openssl/pem.h>
#include <openssl/err.h>
+#include <openssl/conf.h>
+#include <openssl/e_os.h>
+#include <openssl/rand.h>
+
+#define DEFAULT_KEY_LENGTH 512
+#define MIN_KEY_LENGTH 384
+
#define DEBUG_SMIME 0
static unsigned char arg2and3of4_force_ref[] =
{ 4, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_NONE };
+enum php_openssl_key_type {
+ OPENSSL_KEYTYPE_RSA,
+ OPENSSL_KEYTYPE_DSA,
+ OPENSSL_KEYTYPE_DH,
+ OPENSSL_KEYTYPE_DEFAULT = OPENSSL_KEYTYPE_RSA
+};
+
/* {{{ openssl_functions[]
*/
function_entry openssl_functions[] = {
- PHP_FE(openssl_get_privatekey, NULL)
- PHP_FE(openssl_get_publickey, NULL)
- PHP_FE(openssl_free_key, NULL)
-
- PHP_FE(openssl_x509_read, NULL)
+/* public/private key functions */
+ PHP_FE(openssl_pkey_free, NULL)
+ PHP_FE(openssl_pkey_new, NULL)
+ PHP_FE(openssl_pkey_export, second_arg_force_ref)
+ PHP_FE(openssl_pkey_get_private, NULL)
+ PHP_FE(openssl_pkey_get_public, NULL)
+
+ PHP_FALIAS(openssl_free_key, openssl_pkey_free, NULL)
+ PHP_FALIAS(openssl_get_privatekey, openssl_pkey_get_private, NULL)
+ PHP_FALIAS(openssl_get_publickey, openssl_pkey_get_public, NULL)
+
+/* x.509 cert funcs */
+ PHP_FE(openssl_x509_read, NULL)
PHP_FE(openssl_x509_free, NULL)
-
- PHP_FE(openssl_x509_parse, NULL)
+ PHP_FE(openssl_x509_parse, NULL)
PHP_FE(openssl_x509_checkpurpose, NULL)
+ PHP_FE(openssl_x509_check_private_key, NULL)
+ PHP_FE(openssl_x509_export, second_arg_force_ref)
+/* CSR funcs */
+ PHP_FE(openssl_csr_new, second_arg_force_ref)
+ PHP_FE(openssl_csr_export, second_arg_force_ref)
+ PHP_FE(openssl_csr_sign, NULL)
+
+
PHP_FE(openssl_sign, arg2of3_force_ref)
PHP_FE(openssl_verify, NULL)
PHP_FE(openssl_seal, arg2and3of4_force_ref)
PHP_FE(openssl_open, arg2of4_force_ref)
+
/* for S/MIME handling */
PHP_FE(openssl_pkcs7_verify, NULL)
PHP_FE(openssl_pkcs7_decrypt, NULL)
ZEND_GET_MODULE(openssl)
#endif
-static void _php_pkey_free(zend_rsrc_list_entry *rsrc TSRMLS_DC);
static int le_key;
-
-static void _php_x509_free(zend_rsrc_list_entry *rsrc TSRMLS_DC);
static int le_x509;
+static int le_csr;
+
+/* {{{ resource destructors */
+static void php_pkey_free(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+ EVP_PKEY *pkey = (EVP_PKEY *)rsrc->ptr;
+ EVP_PKEY_free(pkey);
+}
+
+static void php_x509_free(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+ X509 *x509 = (X509 *)rsrc->ptr;
+ X509_free(x509);
+}
+
+static void php_csr_free(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+ X509_REQ * csr = (X509_REQ*)rsrc->ptr;
+ X509_REQ_free(csr);
+}
+/* }}} */
+
+/* {{{ openssl -> PHP "bridging" */
+/* true global; readonly after module startup */
+static char default_ssl_conf_filename[PATH_MAX];
+
+struct php_x509_request {
+ LHASH * global_config; /* Global SSL config */
+ LHASH * req_config; /* SSL config for this request */
+ const EVP_MD * md_alg;
+ const EVP_MD * digest;
+ char * section_name,
+ * config_filename,
+ * digest_name,
+ * extensions_section,
+ * request_extensions_section;
+ int priv_key_bits;
+ int priv_key_type;
+
+ int priv_key_encrypt;
+
+ EVP_PKEY * priv_key;
+};
+
static X509 * php_openssl_x509_from_zval(zval ** val, int makeresource, long * resourceval TSRMLS_DC);
static EVP_PKEY * php_openssl_evp_from_zval(zval ** val, int public_key, char * passphrase, int makeresource, long * resourceval TSRMLS_DC);
static X509_STORE * setup_verify(zval * calist TSRMLS_DC);
static STACK_OF(X509) * load_all_certs_from_file(char *certfile);
+static X509_REQ * php_openssl_csr_from_zval(zval ** val, int makeresource, long * resourceval TSRMLS_DC);
+static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req TSRMLS_DC);
+
+
+static void add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname)
+{
+ zval * subitem;
+ int i;
+ char * sn, * ln;
+ int nid;
+ X509_NAME_ENTRY * ne;
+ ASN1_STRING * str;
+ ASN1_OBJECT * obj;
+
+ MAKE_STD_ZVAL(subitem);
+ array_init(subitem);
+
+ for (i = 0; i < X509_NAME_entry_count(name); i++) {
+ ne = X509_NAME_get_entry(name, i);
+ obj = X509_NAME_ENTRY_get_object(ne);
+ str = X509_NAME_ENTRY_get_data(ne);
+ nid = OBJ_obj2nid(obj);
+ if (shortname) {
+ sn = (char*)OBJ_nid2sn(nid);
+ add_assoc_stringl(subitem, sn, str->data, str->length, 1);
+ }
+ else {
+ ln = (char*)OBJ_nid2ln(nid);
+ add_assoc_stringl(subitem, ln, str->data, str->length, 1);
+ }
+ }
+ zend_hash_update(HASH_OF(val), key, strlen(key) + 1, (void *)&subitem, sizeof(subitem), NULL);
+}
+
+static void add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str)
+{
+ add_assoc_stringl(val, key, str->data, str->length, 1);
+}
+
+static time_t asn1_time_to_time_t(ASN1_UTCTIME * timestr TSRMLS_DC)
+{
+/*
+ This is how the time string is formatted:
+
+ sprintf(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;
+
+ if (timestr->length < 13) {
+ zend_error(E_WARNING, "%s(): extension author too lazy to parse %s correctly", get_active_function_name(TSRMLS_C), timestr->data);
+ return (time_t)-1;
+ }
+
+ strbuf = estrdup(timestr->data);
+
+
+ memset(&thetime, 0, sizeof(thetime));
+
+ /* we work backwards so that we can use atoi more easily */
+
+ thestr = strbuf + timestr->length - 3;
+
+ thetime.tm_sec = atoi(thestr);
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_min = atoi(thestr);
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_hour = atoi(thestr);
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_mday = atoi(thestr);
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_mon = atoi(thestr)-1;
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_year = atoi(thestr);
+
+ if (thetime.tm_year < 68)
+ thetime.tm_year += 100;
+
+ thetime.tm_isdst = -1;
+ ret = mktime(&thetime);
+
+#if HAVE_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 ? timezone - 3600 : timezone + 3600);
+#endif
+ ret += gmadjust;
+
+ efree(strbuf);
+
+ return ret;
+}
+
+static inline int php_openssl_config_check_syntax(
+ const char * section_label,
+ const char * config_filename,
+ const char * section,
+ LHASH * config TSRMLS_DC)
+{
+ X509V3_CTX ctx;
+
+ X509V3_set_ctx_test(&ctx);
+ X509V3_set_conf_lhash(&ctx, config);
+ if (!X509V3_EXT_add_conf(config, &ctx, (char *)section, NULL)) {
+ zend_error(E_WARNING, "%s(): Error loading %s section %s of %s",
+ get_active_function_name(TSRMLS_C),
+ section_label,
+ section,
+ config_filename);
+ return FAILURE;
+ }
+ return SUCCESS;
+}
+
+static int add_oid_section(struct php_x509_request * req TSRMLS_DC)
+{
+ char * str;
+ STACK_OF(CONF_VALUE) * sktmp;
+ CONF_VALUE * cnf;
+ int i;
+
+ str = CONF_get_string(req->req_config, NULL, "oid_section");
+ if (str == NULL)
+ return SUCCESS;
+
+ sktmp = CONF_get_section(req->req_config, str);
+ if (sktmp == NULL) {
+ zend_error(E_WARNING, "%s(): problem loading oid section %s",
+ get_active_function_name(TSRMLS_C), str);
+ return FAILURE;
+ }
+ for (i = 0; i < sk_CONF_VALUE_num(sktmp); i++) {
+ cnf = sk_CONF_VALUE_value(sktmp, i);
+ if (OBJ_create(cnf->value, cnf->name, cnf->name) == NID_undef) {
+ zend_error(E_WARNING, "%s(): problem creating object %s=%s",
+ get_active_function_name(TSRMLS_C),
+ cnf->name, cnf->value);
+ return FAILURE;
+ }
+ }
+ return SUCCESS;
+}
+
+#define PHP_SSL_REQ_INIT(req) memset(req, 0, sizeof(*req))
+#define PHP_SSL_REQ_DISPOSE(req) php_openssl_dispose_config(req TSRMLS_C)
+#define PHP_SSL_REQ_PARSE(req, zval) php_openssl_parse_config(req, zval TSRMLS_C)
+
+#define PHP_SSL_CONFIG_SYNTAX_CHECK(var) if (req->var && php_openssl_config_check_syntax(#var, \
+ req->config_filename, req->var, req->req_config TSRMLS_C) == FAILURE) return FAILURE
+
+#define SET_OPTIONAL_STRING_ARG(key, varname, defval) \
+ if (optional_args && zend_hash_find(Z_ARRVAL_P(optional_args), key, sizeof(key), (void**)&item) == SUCCESS) \
+ varname = Z_STRVAL_PP(item); \
+ else \
+ varname = defval
+
+#define SET_OPTIONAL_LONG_ARG(key, varname, defval) \
+ if (optional_args && zend_hash_find(Z_ARRVAL_P(optional_args), key, sizeof(key), (void**)&item) == SUCCESS) \
+ varname = Z_LVAL_PP(item); \
+ else \
+ varname = defval
+
+
+
+static int php_openssl_parse_config(
+ struct php_x509_request * req,
+ zval * optional_args
+ TSRMLS_DC
+ )
+{
+ char * str;
+ 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 = CONF_load(NULL, default_ssl_conf_filename, NULL);
+ req->req_config = CONF_load(NULL, req->config_filename, NULL);
+
+ if (req->req_config == NULL)
+ return FAILURE;
+
+
+ /* read in the oids */
+ str = CONF_get_string(req->req_config, NULL, "oid_file");
+ if (str) {
+ BIO * oid_bio = BIO_new_file(str, "r");
+ if (oid_bio) {
+ OBJ_create_objects(oid_bio);
+ BIO_free(oid_bio);
+ }
+ }
+ if (add_oid_section(req TSRMLS_C) == FAILURE)
+ return FAILURE;
+
+ SET_OPTIONAL_STRING_ARG("digest_alg", req->digest_name,
+ CONF_get_string(req->req_config, req->section_name, "default_md"));
+ SET_OPTIONAL_STRING_ARG("x509_extensions", req->extensions_section,
+ CONF_get_string(req->req_config, req->section_name, "x509_extensions"));
+ SET_OPTIONAL_STRING_ARG("req_extensions", req->extensions_section,
+ CONF_get_string(req->req_config, req->request_extensions_section, "req_extensions"));
+ SET_OPTIONAL_LONG_ARG("private_key_bits", req->priv_key_bits,
+ 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 && zend_hash_find(Z_ARRVAL_P(optional_args), "encrypt_key", sizeof("encrypt_key"), (void**)&item) == SUCCESS) {
+ req->priv_key_encrypt = Z_BVAL_PP(item);
+ }
+ else {
+ str = CONF_get_string(req->req_config, req->section_name, "encrypt_rsa_key");
+ if (str == NULL)
+ str = CONF_get_string(req->req_config, req->section_name, "encrypt_key");
+ if (str && strcmp(str, "no") == 0)
+ req->priv_key_encrypt = 0;
+ else
+ req->priv_key_encrypt = 1;
+ }
+
+
+ /* digest alg */
+ if (req->digest_name == NULL)
+ req->digest_name = CONF_get_string(req->req_config, req->section_name, "default_md");
+ if (req->digest_name)
+ req->digest = req->md_alg = EVP_get_digestbyname(req->digest_name);
+ if (req->md_alg == NULL)
+ req->md_alg = req->digest = EVP_md5();
+
+ PHP_SSL_CONFIG_SYNTAX_CHECK(extensions_section);
+
+ /* 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)) {
+ zend_error(E_WARNING, "%s(): Invalid global string mask setting %s",
+ get_active_function_name(TSRMLS_C), str);
+ return FAILURE;
+ }
+
+ if (req->request_extensions_section == NULL)
+ req->request_extensions_section = CONF_get_string(req->req_config, req->section_name, "req_extensions");
+
+ PHP_SSL_CONFIG_SYNTAX_CHECK(request_extensions_section);
+
+ return SUCCESS;
+}
+
+static void php_openssl_dispose_config(struct php_x509_request * req TSRMLS_DC)
+{
+ if (req->priv_key) {
+ EVP_PKEY_free(req->priv_key);
+ req->priv_key = NULL;
+ }
+ if (req->global_config) {
+ CONF_free(req->global_config);
+ req->global_config = NULL;
+ }
+ if (req->req_config) {
+ CONF_free(req->req_config);
+ req->req_config = NULL;
+ }
+}
+
+static int php_openssl_load_rand_file(const char * file, int *egdsocket, int *seeded)
+{
+ char buffer[PATH_MAX];
+
+ *egdsocket = 0;
+ *seeded = 0;
+
+#ifdef WINDOWS
+ RAND_screen();
+#endif
+ if (file == NULL)
+ file = RAND_file_name(buffer, sizeof(buffer));
+ 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;
+ }
+ if (file == NULL || !RAND_load_file(file, -1)) {
+ if (RAND_status() == 0) {
+ zend_error(E_WARNING, "unable to load random state; not enough random data!");
+ return FAILURE;
+ }
+ return FAILURE;
+ }
+ *seeded = 1;
+ return SUCCESS;
+}
+
+static int php_openssl_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 FAILURE;
+ }
+ if (file == NULL)
+ file = RAND_file_name(buffer, sizeof(buffer));
+ if (file == NULL || !RAND_write_file(file)) {
+ zend_error(E_WARNING, "unable to write random state");
+ return FAILURE;
+ }
+ return SUCCESS;
+}
+/* }}} */
/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(openssl)
{
- le_key = zend_register_list_destructors_ex(_php_pkey_free, NULL, "OpenSSL key", module_number);
- le_x509 = zend_register_list_destructors_ex(_php_x509_free, NULL, "OpenSSL X.509", module_number);
+ char * config_filename;
+
+ le_key = zend_register_list_destructors_ex(php_pkey_free, NULL, "OpenSSL key", module_number);
+ le_x509 = zend_register_list_destructors_ex(php_x509_free, NULL, "OpenSSL X.509", module_number);
+ le_csr = zend_register_list_destructors_ex(php_csr_free, NULL, "OpenSSL X.509 CSR", module_number);
OpenSSL_add_all_ciphers();
+ OpenSSL_add_all_digests();
+ OpenSSL_add_all_algorithms();
-/*
- SSL_load_error_strings();
-*/
ERR_load_ERR_strings();
ERR_load_crypto_strings();
ERR_load_EVP_strings();
RSA_PKCS1_OAEP_PADDING,
CONST_CS|CONST_PERSISTENT);
+ /* Values for key types */
+ REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_RSA",
+ OPENSSL_KEYTYPE_RSA, CONST_CS | CONST_PERSISTENT);
+#ifndef NO_DSA
+ REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_DSA",
+ OPENSSL_KEYTYPE_DSA, CONST_CS | CONST_PERSISTENT);
+#endif
+ REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_DH",
+ OPENSSL_KEYTYPE_DH, CONST_CS | CONST_PERSISTENT);
+
+ /* Determine default SSL configuration file */
+ config_filename = getenv("OPENSSL_CONF");
+ if (config_filename == NULL)
+ config_filename = getenv("SSLEAY_CONF");
+ if (config_filename == NULL) {
+ snprintf(default_ssl_conf_filename, sizeof(default_ssl_conf_filename), "%s/%s",
+ X509_get_default_cert_area(),
+ OPENSSL_CONF);
+ }
+ else
+ strncpy(default_ssl_conf_filename, config_filename, sizeof(default_ssl_conf_filename));
+
return SUCCESS;
}
/* }}} */
}
/* }}} */
+/* {{{ x509 cert functions */
+
/* {{{ php_openssl_x509_from_zval
Given a zval, coerce it into an X509 object.
The zval can be:
}
return cert;
}
+
/* }}} */
-/* {{{ php_openssl_evp_from_zval
- Given a zval, 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)
- NOTE: If you are requesting a private key but have not specified a passphrase, you should use an
- empty string rather than NULL for the passphrase - NULL causes a passphrase prompt to be emitted in
- the Apache error log!
-*/
-static EVP_PKEY * php_openssl_evp_from_zval(zval ** val, int public_key, char * passphrase, int makeresource, long * resourceval TSRMLS_DC)
+/* {{{ proto openssl_x509_export(mixed x509, string &out, bool tofile[, bool notext = true])
+ Export a cert to file or a var */
+PHP_FUNCTION(openssl_x509_export)
{
- EVP_PKEY * key = NULL;
- X509 * cert = NULL;
- int free_cert = 0;
- long cert_res = -1;
- char * filename = NULL;
-
- if (resourceval)
- *resourceval = -1;
+ X509 * cert;
+ zval * zcert = NULL, *zout=NULL;
+ zend_bool tofile = 0, notext = 1;
+ BIO * bio_out;
+ long certresource;
- if (Z_TYPE_PP(val) == IS_ARRAY) {
- zval ** zphrase;
-
- /* get passphrase */
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rzb|b", &zcert, &zout, &tofile, ¬ext) == FAILURE)
+ return;
- if (zend_hash_index_find(HASH_OF(*val), 1, (void **)&zphrase) == FAILURE) {
- zend_error(E_WARNING, "%s(): key array must be of the form array(0 => key, 1 => phrase)", get_active_function_name(TSRMLS_C));
- return NULL;
- }
- convert_to_string_ex(zphrase);
- passphrase = Z_STRVAL_PP(zphrase);
+ RETVAL_FALSE;
- /* now set val to be the key param and continue */
- if (zend_hash_index_find(HASH_OF(*val), 0, (void **)&val) == FAILURE) {
- zend_error(E_WARNING, "%s(): key array must be of the form array(0 => key, 1 => phrase)", get_active_function_name(TSRMLS_C));
- return NULL;
+ cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
+ if (cert == NULL) {
+ zend_error(E_WARNING, "cannot get cert from parameter 1");
+ return;
+ }
+
+ if (tofile) {
+ /* TODO: export to named file */
+ bio_out = BIO_new_file(Z_STRVAL_P(zout), "w");
+ if (bio_out) {
+ if (!notext)
+ X509_print(bio_out, cert);
+ PEM_write_bio_X509(bio_out, cert);
+
+ RETVAL_TRUE;
}
+ else
+ zend_error(E_WARNING, "error opening file %s", Z_STRVAL_P(zout));
}
+ else {
+ /* export to a var */
+ char * bio_mem_ptr;
+ long bio_mem_len;
- if (Z_TYPE_PP(val) == IS_RESOURCE) {
- void * what;
- int type;
+ bio_out = BIO_new(BIO_s_mem());
+ if (!notext)
+ X509_print(bio_out, cert);
+ PEM_write_bio_X509(bio_out, cert);
- what = zend_fetch_resource(val TSRMLS_CC, -1, "OpenSSL X.509/key", &type, 2, le_x509, le_key);
- if (!what)
- return NULL;
+ bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr);
+ ZVAL_STRINGL(zout, bio_mem_ptr, bio_mem_len, 1);
- if (resourceval)
- *resourceval = Z_LVAL_PP(val);
+ RETVAL_TRUE;
+ }
+
+ if (certresource == -1 && cert)
+ X509_free(cert);
+
+ BIO_free(bio_out);
- if (type == le_x509) {
- /* extract key from cert, depending on public_key param */
- cert = (X509*)what;
- free_cert = 0;
- }
- else if (type == le_key) {
- /* got the key - return it */
- return (EVP_PKEY*)what;
- }
+}
+/* }}} */
- /* other types could be used here - eg: file pointers and read in the data from them */
+/* {{{ proto bool openssl_x509_check_private_key(mixed cert, mixed key)
+ Checks if a private key corresponds to a cert */
+PHP_FUNCTION(openssl_x509_check_private_key)
+{
+ zval * zcert, *zkey;
+ X509 * cert = NULL;
+ EVP_PKEY * key = NULL;
+ long certresource = -1, keyresource = -1;
- return NULL;
+ RETVAL_FALSE;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &zcert, &zkey) == FAILURE)
+ return;
+
+ cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
+ if (cert == NULL)
+ RETURN_FALSE;
+
+ key = php_openssl_evp_from_zval(&zkey, 0, "", 1, &keyresource TSRMLS_CC);
+ if (key) {
+ RETVAL_BOOL(X509_check_private_key(cert, key));
}
- else {
- /* force it to be a string and check if it refers to a file */
- convert_to_string_ex(val);
- if (Z_STRLEN_PP(val) > 7 && memcmp(Z_STRVAL_PP(val), "file://", 7) == 0)
- filename = Z_STRVAL_PP(val) + 7;
+ if (keyresource == -1 && key)
+ EVP_PKEY_free(key);
+ if (certresource == -1 && cert)
+ X509_free(cert);
+}
+/* }}} */
- /* it's an X509 file/cert of some kind, and we need to extract the data from that */
- if (public_key) {
- cert = php_openssl_x509_from_zval(val, 0, &cert_res TSRMLS_CC);
- free_cert = (cert_res == -1);
- /* actual extraction done later */
- }
- else {
- /* we want the private key */
- if (filename) {
- BIO *in = BIO_new_file(filename, "r");
- if (in == NULL)
- return NULL;
- key = PEM_read_bio_PrivateKey(in, NULL,NULL, passphrase);
- BIO_free(in);
- }
- else {
- BIO * b = BIO_new_mem_buf(Z_STRVAL_PP(val), Z_STRLEN_PP(val));
- if (b == NULL)
- return NULL;
+/* {{{ proto array openssl_x509_parse(mixed x509[, bool shortnames=true])
+ returns an array of the fields/values of the cert */
+PHP_FUNCTION(openssl_x509_parse)
+{
+ zval * zcert;
+ X509 * cert = NULL;
+ long certresource = -1;
+ int i;
+ zend_bool useshortnames = 1;
+ char * tmpstr;
+ zval * subitem;
- key = (EVP_PKEY *) PEM_ASN1_read_bio((char *(*)())d2i_PrivateKey,
- PEM_STRING_EVP_PKEY, b,
- NULL, NULL, passphrase);
- BIO_free(b);
- }
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|b", &zcert, &useshortnames) == FAILURE)
+ return;
+
+ cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
+ if (cert == NULL)
+ RETURN_FALSE;
+
+ array_init(return_value);
+
+ if (cert->name)
+ add_assoc_string(return_value, "name", cert->name, 1);
+/* add_assoc_bool(return_value, "valid", cert->valid); */
+
+ add_assoc_name_entry(return_value, "subject", X509_get_subject_name(cert), useshortnames);
+ add_assoc_name_entry(return_value, "issuer", X509_get_issuer_name(cert), useshortnames);
+ add_assoc_long(return_value, "version", X509_get_version(cert));
+ add_assoc_long(return_value, "serialNumber", ASN1_INTEGER_get(X509_get_serialNumber(cert)));
+
+ add_assoc_asn1_string(return_value, "validFrom", X509_get_notBefore(cert));
+ add_assoc_asn1_string(return_value, "validTo", X509_get_notAfter(cert));
+
+ add_assoc_long(return_value, "validFrom_time_t", asn1_time_to_time_t(X509_get_notBefore(cert) TSRMLS_CC));
+ add_assoc_long(return_value, "validTo_time_t", asn1_time_to_time_t(X509_get_notAfter(cert) TSRMLS_CC));
+
+ tmpstr = X509_alias_get0(cert, NULL);
+ if (tmpstr)
+ add_assoc_string(return_value, "alias", tmpstr, 1);
+
+/*
+ add_assoc_long(return_value, "signaturetypeLONG", X509_get_signature_type(cert));
+ add_assoc_string(return_value, "signaturetype", OBJ_nid2sn(X509_get_signature_type(cert)), 1);
+ add_assoc_string(return_value, "signaturetypeLN", OBJ_nid2ln(X509_get_signature_type(cert)), 1);
+*/
+ MAKE_STD_ZVAL(subitem);
+ array_init(subitem);
+
+ /* NOTE: the purposes are added as integer keys - the keys match up to the X509_PURPOSE_SSL_XXX defines
+ in x509v3.h */
+ for (i = 0; i < X509_PURPOSE_get_count(); i++) {
+ int id, purpset;
+ char * pname;
+ X509_PURPOSE * purp;
+ zval * subsub;
+
+ MAKE_STD_ZVAL(subsub);
+ array_init(subsub);
+
+ purp = X509_PURPOSE_get0(i);
+ id = X509_PURPOSE_get_id(purp);
+
+ purpset = X509_check_purpose(cert, id, 0);
+ add_index_bool(subsub, 0, purpset);
+
+ purpset = X509_check_purpose(cert, id, 1);
+ add_index_bool(subsub, 1, purpset);
+
+ pname = useshortnames ? X509_PURPOSE_get0_sname(purp) : X509_PURPOSE_get0_name(purp);
+ add_index_string(subsub, 2, pname, 1);
+
+ /* NOTE: if purpset > 1 then it's a warning - we should mention it ? */
+
+ add_index_zval(subitem, id, subsub);
+ }
+ add_assoc_zval(return_value, "purposes", subitem);
+
+ if (certresource == -1 && cert)
+ X509_free(cert);
+
+}
+/* }}} */
+
+/* {{{ load_all_certs_from_file */
+static STACK_OF(X509) * load_all_certs_from_file(char *certfile)
+{
+ STACK_OF(X509_INFO) *sk=NULL;
+ STACK_OF(X509) *stack=NULL, *ret=NULL;
+ BIO *in=NULL;
+ X509_INFO *xi;
+ TSRMLS_FETCH();
+
+ if(!(stack = sk_X509_new_null())) {
+ zend_error(E_ERROR, "%s(): memory allocation failure", get_active_function_name(TSRMLS_C));
+ goto end;
+ }
+
+ if(!(in=BIO_new_file(certfile, "r"))) {
+ zend_error(E_WARNING, "%s(): error opening the file, %s", get_active_function_name(TSRMLS_C), certfile);
+ 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))) {
+ zend_error(E_WARNING, "%s(): error reading the file, %s", get_active_function_name(TSRMLS_C), certfile);
+ 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)) {
+ zend_error(E_WARNING, "%s(): no certificates in file, %s", get_active_function_name(TSRMLS_C), certfile);
+ sk_X509_free(stack);
+ goto end;
+ }
+ ret=stack;
+end:
+ BIO_free(in);
+ sk_X509_INFO_free(sk);
- if (public_key && cert && key == NULL) {
- /* extract public key from X509 cert */
- key = (EVP_PKEY *) X509_get_pubkey(cert);
+ return ret;
+}
+/* }}} */
+
+/* {{{ check_cert */
+static int check_cert(X509_STORE *ctx, X509 *x, STACK_OF(X509) *untrustedchain, int purpose)
+{
+ int ret=0;
+ X509_STORE_CTX *csc;
+ TSRMLS_FETCH();
+
+ csc = X509_STORE_CTX_new();
+ if (csc == NULL)
+ {
+ zend_error(E_ERROR, "%s(): memory allocation failure", get_active_function_name(TSRMLS_C));
+ return 0;
}
+ X509_STORE_CTX_init(csc, ctx, x, untrustedchain);
- if (free_cert && cert)
+ if(purpose >= 0)
+ X509_STORE_CTX_set_purpose(csc, purpose);
+
+ ret = X509_verify_cert(csc);
+ X509_STORE_CTX_free(csc);
+
+ return ret;
+}
+/* }}} */
+
+/* {{{ proto int openssl_x509_checkpurpose(mixed x509cert, int purpose, array cainfo[, string untrustedfile])
+ check the cert to see if it can be used for the purpose in purpose. cainfo holds information about trusted CAs */
+PHP_FUNCTION(openssl_x509_checkpurpose)
+{
+ zval * zcert, * zcainfo;
+ X509_STORE * cainfo = NULL;
+ X509 * cert = NULL;
+ long certresource = -1;
+ STACK_OF(X509) * untrustedchain = NULL;
+ long purpose;
+ char * untrusted = NULL;
+ long untrusted_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zla|s", &zcert, &purpose, &zcainfo, &untrusted, &untrusted_len)
+ == FAILURE)
+ return;
+
+ RETVAL_LONG(-1);
+
+ if (untrusted) {
+ untrustedchain = load_all_certs_from_file(untrusted);
+ if (untrustedchain == NULL)
+ goto clean_exit;
+ }
+
+ cainfo = setup_verify(zcainfo TSRMLS_CC);
+ if (cainfo == NULL)
+ goto clean_exit;
+
+ cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
+ if (cert == NULL)
+ goto clean_exit;
+
+ RETVAL_BOOL(check_cert(cainfo, cert, untrustedchain, purpose));
+
+clean_exit:
+ if (certresource == 1 && cert)
X509_free(cert);
+ if (cainfo)
+ X509_STORE_free(cainfo);
+ if (untrustedchain)
+ sk_X509_pop_free(untrustedchain, X509_free);
+}
+/* }}} */
- if (key && makeresource && resourceval) {
- *resourceval = zend_list_insert(key, le_key);
+/* {{{ 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.
+*/
+static X509_STORE * setup_verify(zval * calist TSRMLS_DC)
+{
+ X509_STORE *store;
+ X509_LOOKUP * dir_lookup, * file_lookup;
+ HashPosition pos;
+ int ndirs = 0, nfiles = 0;
+
+ store = X509_STORE_new();
+
+ if (store == NULL)
+ return NULL;
+
+ if (calist && (Z_TYPE_P(calist) == IS_ARRAY)) {
+ zend_hash_internal_pointer_reset_ex(HASH_OF(calist), &pos);
+ for (;; zend_hash_move_forward_ex(HASH_OF(calist), &pos)) {
+ zval ** item;
+ struct stat sb;
+
+ if (zend_hash_get_current_data_ex(HASH_OF(calist), (void**)&item, &pos) == FAILURE)
+ break;
+
+ convert_to_string_ex(item);
+
+ if (VCWD_STAT(Z_STRVAL_PP(item), &sb) == -1) {
+ zend_error(E_WARNING, "%s() unable to stat %s", get_active_function_name(TSRMLS_C), Z_STRVAL_PP(item));
+ 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, Z_STRVAL_PP(item), X509_FILETYPE_PEM))
+ zend_error(E_WARNING, "%s() error loading file %s", get_active_function_name(TSRMLS_C), Z_STRVAL_PP(item));
+ 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, Z_STRVAL_PP(item), X509_FILETYPE_PEM))
+ zend_error(E_WARNING, "%s() error loading directory %s", get_active_function_name(TSRMLS_C), Z_STRVAL_PP(item));
+ else
+ ndirs++;
+ dir_lookup = NULL;
+ }
+ }
+ }
+ if (nfiles == 0) {
+ file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+ if (file_lookup)
+ X509_LOOKUP_load_file(file_lookup, NULL, X509_FILETYPE_DEFAULT);
+ }
+ if (ndirs == 0) {
+ dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
+ if (dir_lookup)
+ X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT);
}
- return key;
+ return store;
}
/* }}} */
-/* {{{ proto bool openssl_private_encrypt(string data, string crypted, mixed key [, int padding])
- Encrypt data with private key */
-PHP_FUNCTION(openssl_private_encrypt)
+/* {{{ proto resource openssl_x509_read(mixed cert)
+ Read X.509 certificate */
+PHP_FUNCTION(openssl_x509_read)
{
- zval *key, *crypted;
- EVP_PKEY *pkey;
- int cryptedlen;
- unsigned char *cryptedbuf = NULL;
- int successful = 0;
- long keyresource = -1;
- char * data;
- long data_len, padding = RSA_PKCS1_PADDING;
+ zval *cert;
+ X509 *x509;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE)
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &cert) == FAILURE)
return;
- RETVAL_FALSE;
-
- pkey = php_openssl_evp_from_zval(&key, 0, "", 0, &keyresource TSRMLS_CC);
+ Z_TYPE_P(return_value) = IS_RESOURCE;
+ x509 = php_openssl_x509_from_zval(&cert, 1, &Z_LVAL_P(return_value) TSRMLS_CC);
- if (pkey == NULL) {
- zend_error(E_WARNING, "%s(): key param is not a valid private key",
- get_active_function_name(TSRMLS_C));
+ if (x509 == NULL) {
+ zend_error(E_WARNING, "%s() supplied parameter cannot be coerced into an X509 certificate!", get_active_function_name(TSRMLS_C));
RETURN_FALSE;
}
-
- cryptedlen = EVP_PKEY_size(pkey);
- cryptedbuf = emalloc(cryptedlen + 1);
-
- switch (pkey->type) {
- case EVP_PKEY_RSA:
- case EVP_PKEY_RSA2:
- successful = (RSA_private_encrypt(data_len,
- data,
- cryptedbuf,
- pkey->pkey.rsa,
- padding) == cryptedlen);
- break;
- default:
- zend_error(E_WARNING, "%s(): key type not supported in this PHP build!");
- }
-
- if (successful) {
- zval_dtor(crypted);
- cryptedbuf[cryptedlen] = '\0';
- ZVAL_STRINGL(crypted, cryptedbuf, cryptedlen, 0);
- cryptedbuf = NULL;
- RETVAL_TRUE;
- }
- if (cryptedbuf)
- efree(cryptedbuf);
- if (keyresource == -1)
- EVP_PKEY_free(pkey);
}
/* }}} */
-/* {{{ proto bool openssl_private_decrypt(string data, string crypted, mixed key [, int padding])
- Decrypt data with private key */
-PHP_FUNCTION(openssl_private_decrypt)
+/* {{{ proto void openssl_free_x509(resource x509)
+ Free X.509 certificate */
+PHP_FUNCTION(openssl_x509_free)
{
- zval *key, *crypted;
- EVP_PKEY *pkey;
- int cryptedlen;
- unsigned char *cryptedbuf;
- unsigned char *crypttemp;
- int successful = 0;
- long padding = RSA_PKCS1_PADDING;
- long keyresource = -1;
- char * data;
- long data_len;
-
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE)
- return;
-
- RETVAL_FALSE;
-
- pkey = php_openssl_evp_from_zval(&key, 0, "", 0, &keyresource TSRMLS_CC);
- if (pkey == NULL) {
- zend_error(E_WARNING, "%s(): key parameter is not a valid private key", get_active_function_name(TSRMLS_C));
- RETURN_FALSE;
- }
-
- cryptedlen = EVP_PKEY_size(pkey);
- crypttemp = emalloc(cryptedlen + 1);
-
- switch (pkey->type) {
- case EVP_PKEY_RSA:
- case EVP_PKEY_RSA2:
- cryptedlen = RSA_private_decrypt(data_len,
- data,
- crypttemp,
- pkey->pkey.rsa,
- padding);
- if (cryptedlen != -1) {
- cryptedbuf = emalloc(cryptedlen + 1);
- memcpy(cryptedbuf, crypttemp, cryptedlen);
- successful = 1;
- }
- break;
- default:
- zend_error(E_WARNING, "%s(): key type not supported in this PHP build!");
- }
-
- efree(crypttemp);
+ zval *x509;
+ X509 *cert;
- if (successful) {
- zval_dtor(crypted);
- cryptedbuf[cryptedlen] = '\0';
- ZVAL_STRINGL(crypted, cryptedbuf, cryptedlen, 0);
- cryptedbuf = NULL;
- RETVAL_TRUE;
- }
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &x509) == FAILURE)
+ return;
- if (keyresource == -1)
- EVP_PKEY_free(pkey);
- if (cryptedbuf)
- efree(cryptedbuf);
+ ZEND_FETCH_RESOURCE(cert, X509 *, &x509, -1, "OpenSSL X.509", le_x509);
+ zend_list_delete(Z_LVAL_P(x509));
}
/* }}} */
-/* {{{ proto bool openssl_public_encrypt(string data, string crypted, mixed key [, int padding])
- Encrypt data with public key */
-PHP_FUNCTION(openssl_public_encrypt)
+/* }}} */
+
+/* {{{ x509 CSR functions */
+
+/* {{{ php_openssl_make_REQ */
+static int php_openssl_make_REQ(struct php_x509_request * req, X509_REQ * csr, zval * dn, zval * attribs)
{
- zval *key, *crypted;
- EVP_PKEY *pkey;
- int cryptedlen;
- unsigned char *cryptedbuf;
- int successful = 0;
- long keyresource = -1;
- long padding = RSA_PKCS1_PADDING;
- char * data;
- long data_len;
-
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE)
- return;
+ STACK_OF(CONF_VALUE) * dn_sk, *attr_sk = NULL;
+ char * str, *dn_sect, *attr_sect;
+
+ dn_sect = CONF_get_string(req->req_config, req->section_name, "distinguished_name");
+ if (dn_sect == NULL)
+ return FAILURE;
+ dn_sk = CONF_get_section(req->req_config, dn_sect);
+ if (dn_sk == NULL)
+ return FAILURE;
+ attr_sect = CONF_get_string(req->req_config, req->section_name, "attributes");
+ if (attr_sect == NULL)
+ attr_sk = NULL;
+ else {
+ attr_sk = CONF_get_section(req->req_config, attr_sect);
+ if (attr_sk == NULL)
+ 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;
+ HashPosition hpos;
+ zval ** item;
+
+ subj = X509_REQ_get_subject_name(csr);
+ /* apply values from the dn hash */
+ zend_hash_internal_pointer_reset_ex(HASH_OF(dn), &hpos);
+ while(zend_hash_get_current_data_ex(HASH_OF(dn), (void**)&item, &hpos) == SUCCESS) {
+ char * strindex; int strindexlen;
+ long intindex;
+
+ zend_hash_get_current_key_ex(HASH_OF(dn), &strindex, &strindexlen, &intindex, 0, &hpos);
- RETVAL_FALSE;
-
- pkey = php_openssl_evp_from_zval(&key, 1, NULL, 0, &keyresource TSRMLS_CC);
- if (pkey == NULL) {
- zend_error(E_WARNING, "%s(): key parameter is not a valid public key", get_active_function_name(TSRMLS_C));
- RETURN_FALSE;
- }
+ convert_to_string_ex(item);
- cryptedlen = EVP_PKEY_size(pkey);
- cryptedbuf = emalloc(cryptedlen + 1);
+ if (strindex) {
+ int nid;
+
+ nid = OBJ_txt2nid(strindex);
+ if (nid != NID_undef) {
+ if (!X509_NAME_add_entry_by_NID(subj, nid, MBSTRING_ASC,
+ (unsigned char*)Z_STRVAL_PP(item), -1, -1, 0))
+ {
+ zend_error(E_WARNING, "dn: add_entry_by_NID %d -> %s (failed)", nid, Z_STRVAL_PP(item));
+ return FAILURE;
+ }
+ }
+ else zend_error(E_WARNING, "dn: %s is not a recognized name", strindex);
+ }
+ zend_hash_move_forward_ex(HASH_OF(dn), &hpos);
+ }
- switch (pkey->type) {
- case EVP_PKEY_RSA:
- case EVP_PKEY_RSA2:
- successful = (RSA_public_encrypt(data_len,
- data,
- cryptedbuf,
- pkey->pkey.rsa,
- padding) == cryptedlen);
- break;
- default:
- zend_error(E_WARNING, "%s(): key type not supported in this PHP build!");
+ /* Finally apply defaults from config file */
+ for(i = 0; i < sk_CONF_VALUE_num(dn_sk); i++) {
+ int len;
+ char buffer[200];
+
+ 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;
+ }
+
+ 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;
+ zend_printf("%s not already set; using default of %s\n", type, v->value);
+ if (!X509_NAME_add_entry_by_txt(subj, type, MBSTRING_ASC, (unsigned char*)v->value, -1, -1, 0))
+ {
+ zend_error(E_WARNING, "add_entry_by_txt %s -> %s (failed)", type, v->value);
+ return FAILURE;
+ }
+ if (!X509_NAME_entry_count(subj)) {
+ zend_error(E_WARNING, "no objects specified in config file");
+ return FAILURE;
+ }
+ }
+ if (attribs) {
+ zend_hash_internal_pointer_reset_ex(HASH_OF(attribs), &hpos);
+ while(zend_hash_get_current_data_ex(HASH_OF(attribs), (void**)&item, &hpos) == SUCCESS) {
+ char * strindex; int strindexlen;
+ long intindex;
+
+ zend_hash_get_current_key_ex(HASH_OF(attribs), &strindex, &strindexlen, &intindex, 0, &hpos);
+ convert_to_string_ex(item);
+
+ if (strindex) {
+ int nid;
+
+ nid = OBJ_txt2nid(strindex);
+ if (nid != NID_undef) {
+ if (!X509_NAME_add_entry_by_NID(subj, nid, MBSTRING_ASC,
+ (unsigned char*)Z_STRVAL_PP(item), -1, -1, 0))
+ {
+ zend_error(E_WARNING, "attribs: add_entry_by_NID %d -> %s (failed)", nid, Z_STRVAL_PP(item));
+ return FAILURE;
+ }
+ }
+ else zend_error(E_WARNING, "dn: %s is not a recognized name", strindex);
+ }
+ zend_hash_move_forward_ex(HASH_OF(attribs), &hpos);
+ }
+ 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_ASC,
+ (unsigned char*)v->value, -1))
+ {
+ zend_error(E_WARNING, "add1_attr_by_txt %s -> %s (failed)", v->name, v->value);
+ return FAILURE;
+ }
+ }
+ }
}
- if (successful) {
- zval_dtor(crypted);
- cryptedbuf[cryptedlen] = '\0';
- ZVAL_STRINGL(crypted, cryptedbuf, cryptedlen, 0);
- cryptedbuf = NULL;
- RETVAL_TRUE;
- }
- if (keyresource == -1)
- EVP_PKEY_free(pkey);
- if (cryptedbuf)
- efree(cryptedbuf);
+ X509_REQ_set_pubkey(csr, req->priv_key);
+ return SUCCESS;
}
/* }}} */
-/* {{{ proto bool openssl_public_decrypt(string data, string crypted, resource key [, int padding])
- Decrypt data with public key */
-PHP_FUNCTION(openssl_public_decrypt)
+/* {{{ php_openssl_csr_from_zval */
+static X509_REQ * php_openssl_csr_from_zval(zval ** val, int makeresource, long * resourceval TSRMLS_DC)
{
- zval *key, *crypted;
- EVP_PKEY *pkey;
- int cryptedlen;
- unsigned char *cryptedbuf;
- unsigned char *crypttemp;
- int successful = 0;
- long keyresource = -1;
- long padding = RSA_PKCS1_PADDING;
- char * data;
- long data_len;
-
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE)
- return;
-
- RETVAL_FALSE;
+ X509_REQ * csr = NULL;
+ char * filename;
+ BIO * in;
- pkey = php_openssl_evp_from_zval(&key, 1, NULL, 0, &keyresource TSRMLS_CC);
- if (pkey == NULL) {
- zend_error(E_WARNING, "%s(): key parameter is not a valid public key", get_active_function_name(TSRMLS_C));
- RETURN_FALSE;
- }
+ if (resourceval)
+ *resourceval = -1;
- cryptedlen = EVP_PKEY_size(pkey);
- crypttemp = emalloc(cryptedlen + 1);
+ if (Z_TYPE_PP(val) == IS_RESOURCE) {
+ void * what;
+ int type;
- switch (pkey->type) {
- case EVP_PKEY_RSA:
- case EVP_PKEY_RSA2:
- cryptedlen = RSA_public_decrypt(data_len,
- data,
- crypttemp,
- pkey->pkey.rsa,
- padding);
- if (cryptedlen != -1) {
- cryptedbuf = emalloc(cryptedlen + 1);
- memcpy(cryptedbuf, crypttemp, cryptedlen);
- successful = 1;
- }
- break;
-
- default:
- zend_error(E_WARNING, "%s(): key type not supported in this PHP build!");
-
+ what = zend_fetch_resource(val TSRMLS_CC, -1, "OpenSSL X.509 CSR", &type, 1, le_csr);
+ if (what) {
+ if (resourceval)
+ *resourceval = Z_LVAL_PP(val);
+ return (X509_REQ*)what;
+ }
+ return NULL;
}
+ convert_to_string_ex(val);
- efree(crypttemp);
+ if (Z_STRLEN_PP(val) > 7 && memcmp(Z_STRVAL_PP(val), "file://", 7) == 0)
+ filename = Z_STRVAL_PP(val) + 7;
- if (successful) {
- zval_dtor(crypted);
- cryptedbuf[cryptedlen] = '\0';
- ZVAL_STRINGL(crypted, cryptedbuf, cryptedlen, 0);
- cryptedbuf = NULL;
- RETVAL_TRUE;
- }
+ if (filename)
+ in = BIO_new_file(filename, "r");
+ else
+ in = BIO_new_mem_buf(Z_STRVAL_PP(val), Z_STRLEN_PP(val));
+
+ csr = PEM_read_bio_X509_REQ(in, NULL,NULL,NULL);
+ BIO_free(in);
- if (cryptedbuf)
- efree(cryptedbuf);
- if (keyresource == -1)
- EVP_PKEY_free(pkey);
+ return csr;
}
/* }}} */
-/* {{{ proto int openssl_get_privatekey(string key [, string passphrase])
- Get private key */
-PHP_FUNCTION(openssl_get_privatekey)
+/* {{{ proto openssl_csr_export(resource csr, string &out, bool tofile, [bool notext=true])
+ Exports a CSR to file or a var */
+PHP_FUNCTION(openssl_csr_export)
{
- EVP_PKEY *pkey;
- zval * key;
- char * passphrase = "";
- int passphrase_len;
+ X509_REQ * csr;
+ zval * zcsr = NULL, *zout=NULL;
+ zend_bool tofile = 0, notext = 1;
+ BIO * bio_out;
+ long csr_resource;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|s", &key, &passphrase, &passphrase_len) == FAILURE)
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rzb|b", &zcsr, &zout, &tofile, ¬ext) == FAILURE)
return;
- return_value->type = IS_RESOURCE;
- pkey = php_openssl_evp_from_zval(&key, 0, passphrase, 1, &Z_LVAL_P(return_value) TSRMLS_CC);
+ RETVAL_FALSE;
- if (pkey == NULL) {
- zend_error(E_WARNING, "%s(): unable to coerce arg to a private key", get_active_function_name(TSRMLS_C));
- RETURN_FALSE;
+ csr = php_openssl_csr_from_zval(&zcsr, 0, &csr_resource TSRMLS_CC);
+ if (csr == NULL) {
+ zend_error(E_WARNING, "cannot get CSR from parameter 1");
+ return;
}
-}
-/* }}} */
-/* {{{ openssl -> PHP "bridging" */
+ if (tofile) {
+ /* TODO: export to named file */
+ bio_out = BIO_new_file(Z_STRVAL_P(zout), "w");
+ if (bio_out) {
+ if (!notext)
+ X509_REQ_print(bio_out, csr);
+ PEM_write_bio_X509_REQ(bio_out, csr);
+ RETVAL_TRUE;
+ }
+ else
+ zend_error(E_WARNING, "error opening file %s", Z_STRVAL_P(zout));
+ }
+ else {
+ /* export to a var */
+ char * bio_mem_ptr;
+ long bio_mem_len;
-static void add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname)
-{
- zval * subitem;
- int i;
- char * sn, * ln;
- int nid;
- X509_NAME_ENTRY * ne;
- ASN1_STRING * str;
- ASN1_OBJECT * obj;
+ bio_out = BIO_new(BIO_s_mem());
+ if (!notext)
+ X509_REQ_print(bio_out, csr);
+ PEM_write_bio_X509_REQ(bio_out, csr);
- MAKE_STD_ZVAL(subitem);
- array_init(subitem);
+ bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr);
+ ZVAL_STRINGL(zout, bio_mem_ptr, bio_mem_len, 1);
- for (i = 0; i < X509_NAME_entry_count(name); i++) {
- ne = X509_NAME_get_entry(name, i);
- obj = X509_NAME_ENTRY_get_object(ne);
- str = X509_NAME_ENTRY_get_data(ne);
- nid = OBJ_obj2nid(obj);
- if (shortname) {
- sn = (char*)OBJ_nid2sn(nid);
- add_assoc_stringl(subitem, sn, str->data, str->length, 1);
- }
- else {
- ln = (char*)OBJ_nid2ln(nid);
- add_assoc_stringl(subitem, ln, str->data, str->length, 1);
- }
+ RETVAL_TRUE;
}
- zend_hash_update(HASH_OF(val), key, strlen(key) + 1, (void *)&subitem, sizeof(subitem), NULL);
-}
-
-static void add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str)
-{
- add_assoc_stringl(val, key, str->data, str->length, 1);
+
+ if (csr_resource == -1 && csr)
+ X509_REQ_free(csr);
+
+ BIO_free(bio_out);
}
+/* }}} */
-static time_t asn1_time_to_time_t(ASN1_UTCTIME * timestr TSRMLS_DC)
+/* {{{ proto openssl_csr_sign(mixed csr, mixed x509, mixed priv_key, long days)
+ Sign a cert with another cert */
+PHP_FUNCTION(openssl_csr_sign)
{
-/*
- This is how the time string is formatted:
+ zval * zcert = NULL, *zcsr, *zpkey, *args = NULL;
+ long num_days;
+ X509 * cert = NULL, *new_cert = NULL;
+ X509_REQ * csr;
+ EVP_PKEY * key, *priv_key;
+ long csr_resource, certresource, keyresource;
+ int i;
+ struct php_x509_request req;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz!zl|a!", &zcsr, &zcert, &zpkey, &num_days, &args) == FAILURE)
+ return;
- sprintf(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);
-*/
+ RETVAL_FALSE;
+ PHP_SSL_REQ_INIT(&req);
+
+ csr = php_openssl_csr_from_zval(&zcsr, 0, &csr_resource TSRMLS_CC);
+ if (csr == NULL) {
+ zend_error(E_WARNING, "cannot get CSR from parameter 1");
+ return;
+ }
+ if (zcert) {
+ cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
+ if (cert == NULL) {
+ zend_error(E_WARNING, "cannot get cert from parameter 2");
+ goto cleanup;
+ }
+ }
+ priv_key = php_openssl_evp_from_zval(&zpkey, 0, "", 1, &keyresource TSRMLS_CC);
+ if (priv_key == NULL) {
+ zend_error(E_WARNING, "cannot get private key from parameter 3");
+ goto cleanup;
+ }
+ if (cert && !X509_check_private_key(cert, key)) {
+ zend_error(E_WARNING, "private key does not correspond to signing cert");
+ goto cleanup;
+ }
+
+ if (PHP_SSL_REQ_PARSE(&req, args) == FAILURE)
+ goto cleanup;
- time_t ret;
- struct tm thetime;
- char * strbuf;
- char * thestr;
- long gmadjust = 0;
+ /* Check that the request matches the signature */
+ key = X509_REQ_get_pubkey(csr);
+ if (key == NULL) {
+ zend_error(E_WARNING, "error unpacking public key");
+ goto cleanup;
+ }
+ i = X509_REQ_verify(csr, key);
- if (timestr->length < 13) {
- zend_error(E_WARNING, "%s(): extension author too lazy to parse %s correctly", get_active_function_name(TSRMLS_C), timestr->data);
- return (time_t)-1;
+ if (i < 0) {
+ zend_error(E_WARNING, "Signature verification problems");
+ goto cleanup;
+ }
+ else if (i==0) {
+ zend_error(E_WARNING, "Signature did not match the certificate request");
+ goto cleanup;
}
+
+ /* Now we can get on with it */
+
+ new_cert = X509_new();
+ if (new_cert == NULL) {
+ zend_error(E_WARNING, "No memory");
+ goto cleanup;
+ }
+ /* Version 3 cert */
+ if (!X509_set_version(new_cert, 3))
+ goto cleanup;
- strbuf = estrdup(timestr->data);
+ /* TODO: Allow specifying */
+ ASN1_INTEGER_set(X509_get_serialNumber(new_cert), 0L);
+
+ X509_set_subject_name(new_cert, X509_REQ_get_subject_name(csr));
+ if (cert == NULL)
+ cert = new_cert;
- memset(&thetime, 0, sizeof(thetime));
+ if (!X509_set_issuer_name(new_cert, X509_get_subject_name(cert)))
+ goto cleanup;
- /* we work backwards so that we can use atoi more easily */
+ X509_gmtime_adj(X509_get_notBefore(new_cert), 0);
+ X509_gmtime_adj(X509_get_notAfter(new_cert), (long)60*60*24*num_days);
+ i = X509_set_pubkey(new_cert, key);
+ if (!i)
+ goto cleanup;
- thestr = strbuf + timestr->length - 3;
+ if (req.request_extensions_section) {
+ X509V3_CTX ctx;
+
+ X509V3_set_ctx(&ctx, cert, new_cert, csr, NULL, 0);
+ X509V3_set_conf_lhash(&ctx, req.req_config);
+ if (!X509V3_EXT_add_conf(req.req_config, &ctx, req.request_extensions_section, new_cert))
+ goto cleanup;
+ }
- thetime.tm_sec = atoi(thestr);
- *thestr = '\0';
- thestr -= 2;
- thetime.tm_min = atoi(thestr);
- *thestr = '\0';
- thestr -= 2;
- thetime.tm_hour = atoi(thestr);
- *thestr = '\0';
- thestr -= 2;
- thetime.tm_mday = atoi(thestr);
- *thestr = '\0';
- thestr -= 2;
- thetime.tm_mon = atoi(thestr)-1;
- *thestr = '\0';
- thestr -= 2;
- thetime.tm_year = atoi(thestr);
+ /* Now sign it */
+ if (!X509_sign(new_cert, priv_key, req.digest)) {
+ zend_error(E_WARNING, "failed to sign it");
+ goto cleanup;
+ }
+
+ /* Succeeded; lets return the cert */
+ RETVAL_RESOURCE(zend_list_insert(new_cert, le_x509));
+ new_cert = NULL;
+
+cleanup:
- if (thetime.tm_year < 68)
- thetime.tm_year += 100;
+ if (cert == new_cert)
+ cert = NULL;
+
+ PHP_SSL_REQ_DISPOSE(&req);
- thetime.tm_isdst = -1;
- ret = mktime(&thetime);
+ if (keyresource == -1 && priv_key)
+ EVP_PKEY_free(priv_key);
+ if (key)
+ EVP_PKEY_free(key);
+ if (csr_resource == -1 && csr)
+ X509_REQ_free(csr);
+ if (certresource == -1 && cert)
+ X509_free(cert);
+ if (new_cert)
+ X509_free(new_cert);
+}
+/* }}} */
-#if HAVE_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 ? timezone - 3600 : timezone + 3600);
-#endif
- ret += gmadjust;
+/* {{{ proto bool openssl_csr_new(array dn, resource &privkey, [array extraattribs, array configargs])
+ Generate a privkey + CSR */
+PHP_FUNCTION(openssl_csr_new)
+{
+ struct php_x509_request req;
+ zval * args = NULL, * dn, *attribs = NULL;
+ zval * out_pkey;
+ X509_REQ * csr = NULL;
+ int we_made_the_key = 1;
+ long key_resource;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "az|a!a!", &dn, &out_pkey, &args, &attribs) == FAILURE)
+ return;
- efree(strbuf);
+ RETVAL_FALSE;
+
+ PHP_SSL_REQ_INIT(&req);
- return ret;
+ if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS)
+ {
+ /* Generate or use a private key */
+ if (Z_TYPE_P(out_pkey) != IS_NULL) {
+ req.priv_key = php_openssl_evp_from_zval(&out_pkey, 0, NULL, 0, &key_resource TSRMLS_CC);
+ if (req.priv_key != NULL)
+ we_made_the_key = 0;
+ }
+ if (req.priv_key == NULL)
+ php_openssl_generate_private_key(&req TSRMLS_CC);
+ if (req.priv_key == NULL)
+ zend_error(E_WARNING, "Unable to generate a private key");
+ else {
+ csr = X509_REQ_new();
+ if (csr) {
+ if (php_openssl_make_REQ(&req, csr, dn, attribs) == SUCCESS) {
+ X509V3_CTX ext_ctx;
+
+ X509V3_set_ctx(&ext_ctx, NULL, NULL, csr, NULL, 0);
+ X509V3_set_conf_lhash(&ext_ctx, req.req_config);
+
+ /* Add extensions */
+ if (req.request_extensions_section && !X509V3_EXT_REQ_add_conf(req.req_config,
+ &ext_ctx, req.request_extensions_section, csr))
+ {
+ zend_error(E_WARNING, "Error loading extension section %s", req.request_extensions_section);
+ }
+ else {
+ RETVAL_TRUE;
+
+ if (X509_REQ_sign(csr, req.priv_key, req.digest)) {
+
+ RETVAL_RESOURCE(zend_list_insert(csr, le_csr));
+ csr = NULL;
+ }
+ else
+ zend_error(E_WARNING, "Error signing request");
+
+ if (we_made_the_key) {
+ /* and a resource for the private key */
+ ZVAL_RESOURCE(out_pkey, zend_list_insert(req.priv_key, le_key));
+ }
+ else if (key_resource != -1)
+ req.priv_key = NULL; /* make sure the cleanup code doesn't zap it! */
+ }
+ }
+ }
+ }
+ }
+ if (csr)
+ X509_REQ_free(csr);
+ PHP_SSL_REQ_DISPOSE(&req);
}
/* }}} */
-/* {{{ proto array openssl_x509_parse(mixed x509[, bool shortnames=true])
- returns an array of the fields/values of the cert */
-PHP_FUNCTION(openssl_x509_parse)
+/* }}} */
+
+/* {{{ EVP Public/Private key functions */
+
+/* {{{ php_openssl_evp_from_zval
+ Given a zval, 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)
+ NOTE: If you are requesting a private key but have not specified a passphrase, you should use an
+ empty string rather than NULL for the passphrase - NULL causes a passphrase prompt to be emitted in
+ the Apache error log!
+*/
+static EVP_PKEY * php_openssl_evp_from_zval(zval ** val, int public_key, char * passphrase, int makeresource, long * resourceval TSRMLS_DC)
{
- zval * zcert;
+ EVP_PKEY * key = NULL;
X509 * cert = NULL;
- long certresource = -1;
- int i;
- zend_bool useshortnames = 1;
- char * tmpstr;
- zval * subitem;
-
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|b", &zcert, &useshortnames))
- return;
+ int free_cert = 0;
+ long cert_res = -1;
+ char * filename = NULL;
- cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
- if (cert == NULL)
- RETURN_FALSE;
-
- array_init(return_value);
-
- if (cert->name)
- add_assoc_string(return_value, "name", cert->name, 1);
-/* add_assoc_bool(return_value, "valid", cert->valid); */
+ if (resourceval)
+ *resourceval = -1;
- add_assoc_name_entry(return_value, "subject", X509_get_subject_name(cert), useshortnames);
- add_assoc_name_entry(return_value, "issuer", X509_get_issuer_name(cert), useshortnames);
- add_assoc_long(return_value, "version", X509_get_version(cert));
- add_assoc_long(return_value, "serialNumber", ASN1_INTEGER_get(X509_get_serialNumber(cert)));
+ if (Z_TYPE_PP(val) == IS_ARRAY) {
+ zval ** zphrase;
+
+ /* get passphrase */
- add_assoc_asn1_string(return_value, "validFrom", X509_get_notBefore(cert));
- add_assoc_asn1_string(return_value, "validTo", X509_get_notAfter(cert));
+ if (zend_hash_index_find(HASH_OF(*val), 1, (void **)&zphrase) == FAILURE) {
+ zend_error(E_WARNING, "%s(): key array must be of the form array(0 => key, 1 => phrase)", get_active_function_name(TSRMLS_C));
+ return NULL;
+ }
+ convert_to_string_ex(zphrase);
+ passphrase = Z_STRVAL_PP(zphrase);
- add_assoc_long(return_value, "validFrom_time_t", asn1_time_to_time_t(X509_get_notBefore(cert) TSRMLS_CC));
- add_assoc_long(return_value, "validTo_time_t", asn1_time_to_time_t(X509_get_notAfter(cert) TSRMLS_CC));
+ /* now set val to be the key param and continue */
+ if (zend_hash_index_find(HASH_OF(*val), 0, (void **)&val) == FAILURE) {
+ zend_error(E_WARNING, "%s(): key array must be of the form array(0 => key, 1 => phrase)", get_active_function_name(TSRMLS_C));
+ return NULL;
+ }
+ }
- tmpstr = X509_alias_get0(cert, NULL);
- if (tmpstr)
- add_assoc_string(return_value, "alias", tmpstr, 1);
+ if (Z_TYPE_PP(val) == IS_RESOURCE) {
+ void * what;
+ int type;
-/*
- add_assoc_long(return_value, "signaturetypeLONG", X509_get_signature_type(cert));
- add_assoc_string(return_value, "signaturetype", OBJ_nid2sn(X509_get_signature_type(cert)), 1);
- add_assoc_string(return_value, "signaturetypeLN", OBJ_nid2ln(X509_get_signature_type(cert)), 1);
-*/
- MAKE_STD_ZVAL(subitem);
- array_init(subitem);
+ what = zend_fetch_resource(val TSRMLS_CC, -1, "OpenSSL X.509/key", &type, 2, le_x509, le_key);
+ if (!what)
+ return NULL;
- /* NOTE: the purposes are added as integer keys - the keys match up to the X509_PURPOSE_SSL_XXX defines
- in x509v3.h */
- for (i = 0; i < X509_PURPOSE_get_count(); i++) {
- int id, purpset;
- char * pname;
- X509_PURPOSE * purp;
- zval * subsub;
+ if (resourceval)
+ *resourceval = Z_LVAL_PP(val);
- MAKE_STD_ZVAL(subsub);
- array_init(subsub);
+ if (type == le_x509) {
+ /* extract key from cert, depending on public_key param */
+ cert = (X509*)what;
+ free_cert = 0;
+ }
+ else if (type == le_key) {
+ /* got the key - return it */
+ return (EVP_PKEY*)what;
+ }
- purp = X509_PURPOSE_get0(i);
- id = X509_PURPOSE_get_id(purp);
+ /* other types could be used here - eg: file pointers and read in the data from them */
- purpset = X509_check_purpose(cert, id, 0);
- add_index_bool(subsub, 0, purpset);
+ return NULL;
+ }
+ else {
+ /* force it to be a string and check if it refers to a file */
+ convert_to_string_ex(val);
- purpset = X509_check_purpose(cert, id, 1);
- add_index_bool(subsub, 1, purpset);
+ if (Z_STRLEN_PP(val) > 7 && memcmp(Z_STRVAL_PP(val), "file://", 7) == 0)
+ filename = Z_STRVAL_PP(val) + 7;
- pname = useshortnames ? X509_PURPOSE_get0_sname(purp) : X509_PURPOSE_get0_name(purp);
- add_index_string(subsub, 2, pname, 1);
+ /* it's an X509 file/cert of some kind, and we need to extract the data from that */
+ if (public_key) {
+ cert = php_openssl_x509_from_zval(val, 0, &cert_res TSRMLS_CC);
+ free_cert = (cert_res == -1);
+ /* actual extraction done later */
+ }
+ else {
+ /* we want the private key */
+ if (filename) {
+ BIO *in = BIO_new_file(filename, "r");
+ if (in == NULL)
+ return NULL;
+ key = PEM_read_bio_PrivateKey(in, NULL,NULL, passphrase);
+ BIO_free(in);
+ }
+ else {
+ BIO * b = BIO_new_mem_buf(Z_STRVAL_PP(val), Z_STRLEN_PP(val));
+ if (b == NULL)
+ return NULL;
- /* NOTE: if purpset > 1 then it's a warning - we should mention it ? */
+ key = (EVP_PKEY *) PEM_ASN1_read_bio((char *(*)())d2i_PrivateKey,
+ PEM_STRING_EVP_PKEY, b,
+ NULL, NULL, passphrase);
+ BIO_free(b);
+ }
+ }
+ }
- add_index_zval(subitem, id, subsub);
+ if (public_key && cert && key == NULL) {
+ /* extract public key from X509 cert */
+ key = (EVP_PKEY *) X509_get_pubkey(cert);
}
- add_assoc_zval(return_value, "purposes", subitem);
- if (certresource == -1 && cert)
+ if (free_cert && cert)
X509_free(cert);
+ if (key && makeresource && resourceval) {
+ *resourceval = zend_list_insert(key, le_key);
+ }
+ return key;
}
/* }}} */
-/* {{{ load_all_certs_from_file */
-static STACK_OF(X509) * load_all_certs_from_file(char *certfile)
+/* {{{ php_openssl_generate_private_key */
+static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req TSRMLS_DC)
{
- STACK_OF(X509_INFO) *sk=NULL;
- STACK_OF(X509) *stack=NULL, *ret=NULL;
- BIO *in=NULL;
- X509_INFO *xi;
- TSRMLS_FETCH();
-
- if(!(stack = sk_X509_new_null())) {
- zend_error(E_ERROR, "%s(): memory allocation failure", get_active_function_name(TSRMLS_C));
- goto end;
- }
-
- if(!(in=BIO_new_file(certfile, "r"))) {
- zend_error(E_WARNING, "%s(): error opening the file, %s", get_active_function_name(TSRMLS_C), certfile);
- 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))) {
- zend_error(E_WARNING, "%s(): error reading the file, %s", get_active_function_name(TSRMLS_C), certfile);
- goto end;
+ char * randfile = NULL;
+ int egdsocket, seeded;
+ EVP_PKEY * return_val = NULL;
+
+ if (req->priv_key_bits < MIN_KEY_LENGTH) {
+ zend_error(E_WARNING, "private key length is too short; it needs to be at least %d bits, not %d",
+ MIN_KEY_LENGTH, req->priv_key_bits);
+ return NULL;
}
- /* 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;
+ randfile = CONF_get_string(req->req_config, req->section_name, "RANDFILE");
+ php_openssl_load_rand_file(randfile, &egdsocket, &seeded);
+
+ if ((req->priv_key = EVP_PKEY_new()) != NULL) {
+ switch(req->priv_key_type) {
+ case OPENSSL_KEYTYPE_RSA:
+ if (EVP_PKEY_assign_RSA(req->priv_key, RSA_generate_key(req->priv_key_bits, 0x10001, NULL, NULL)))
+ return_val = req->priv_key;
+ break;
+ default:
+ zend_error(E_WARNING, "Unsupported private key type");
}
- X509_INFO_free(xi);
- }
- if(!sk_X509_num(stack)) {
- zend_error(E_WARNING, "%s(): no certificates in file, %s", get_active_function_name(TSRMLS_C), certfile);
- sk_X509_free(stack);
- goto end;
}
- ret=stack;
-end:
- BIO_free(in);
- sk_X509_INFO_free(sk);
- return ret;
+ php_openssl_write_rand_file(randfile, egdsocket, seeded);
+
+ if (return_val == NULL) {
+ EVP_PKEY_free(req->priv_key);
+ req->priv_key = NULL;
+ return NULL;
+ }
+
+ return return_val;
}
/* }}} */
-/* {{{ check_cert */
-static int check_cert(X509_STORE *ctx, X509 *x, STACK_OF(X509) *untrustedchain, int purpose)
+/* {{{ proto resource openssl_pkey_new([array configargs])
+ Generate a new private key */
+PHP_FUNCTION(openssl_pkey_new)
{
- int ret=0;
- X509_STORE_CTX *csc;
- TSRMLS_FETCH();
+ struct php_x509_request req;
+ zval * args = NULL;
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &args) == FAILURE)
+ return;
- csc = X509_STORE_CTX_new();
- if (csc == NULL)
+ RETVAL_FALSE;
+
+ PHP_SSL_REQ_INIT(&req);
+
+ if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS)
{
- zend_error(E_ERROR, "%s(): memory allocation failure", get_active_function_name(TSRMLS_C));
- return 0;
+ if (php_openssl_generate_private_key(&req TSRMLS_C)) {
+ /* pass back a key resource */
+ RETVAL_RESOURCE(zend_list_insert(req.priv_key, le_key));
+ /* make sure the cleanup code doesn't zap it! */
+ req.priv_key = NULL;
+ }
}
- X509_STORE_CTX_init(csc, ctx, x, untrustedchain);
-
- if(purpose >= 0)
- X509_STORE_CTX_set_purpose(csc, purpose);
-
- ret = X509_verify_cert(csc);
- X509_STORE_CTX_free(csc);
-
- return ret;
+ PHP_SSL_REQ_DISPOSE(&req);
}
/* }}} */
-/* {{{ proto int openssl_x509_checkpurpose(mixed x509cert, int purpose, array cainfo[, string untrustedfile])
- check the cert to see if it can be used for the purpose in purpose. cainfo holds information about trusted CAs */
-PHP_FUNCTION(openssl_x509_checkpurpose)
+/* {{{ proto bool openssl_pkey_export(mixed key, &mixed out, bool tofile[, string passphrase, array config_args)
+ Get an exportable representation of a key into a string or file */
+PHP_FUNCTION(openssl_pkey_export)
{
- zval * zcert, * zcainfo;
- X509_STORE * cainfo = NULL;
- X509 * cert = NULL;
- long certresource = -1;
- STACK_OF(X509) * untrustedchain = NULL;
- long purpose;
- char * untrusted = NULL;
- long untrusted_len;
-
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zla|s", &zcert, &purpose, &zcainfo, &untrusted, &untrusted_len)
- == FAILURE)
+ struct php_x509_request req;
+ zval * zpkey, * args = NULL, *out;
+ char * passphrase = NULL; long passphrase_len = 0;
+ long key_resource = -1;
+ zend_bool tofile = 0;
+ EVP_PKEY * key;
+ BIO * bio_out = NULL;
+ EVP_CIPHER * cipher;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zzb|s!a!", &zpkey, &out, &tofile, &passphrase, &passphrase_len, &args) == FAILURE)
return;
- RETVAL_LONG(-1);
-
- if (untrusted) {
- untrustedchain = load_all_certs_from_file(untrusted);
- if (untrustedchain == NULL)
- goto clean_exit;
- }
+ RETVAL_FALSE;
- cainfo = setup_verify(zcainfo TSRMLS_CC);
- if (cainfo == NULL)
- goto clean_exit;
+ key = php_openssl_evp_from_zval(&zpkey, 0, passphrase, 0, &key_resource TSRMLS_C);
- cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
- if (cert == NULL)
- goto clean_exit;
+ if (key == NULL) {
+ zend_error(E_WARNING, "cannot get key from parameter 1");
+ RETURN_FALSE;
+ }
+
+ PHP_SSL_REQ_INIT(&req);
- RETVAL_BOOL(check_cert(cainfo, cert, untrustedchain, purpose));
+ if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS)
+ {
+ if (tofile)
+ bio_out = BIO_new_file(Z_STRVAL_P(out), "w");
+ else
+ bio_out = BIO_new(BIO_s_mem());
+
+ if (passphrase && req.priv_key_encrypt)
+ cipher = EVP_des_ede3_cbc();
+ else
+ cipher = NULL;
+
+ if (PEM_write_bio_PrivateKey(bio_out, key, cipher, passphrase, passphrase_len, NULL, NULL)) {
+ /* Success!
+ * If returning the output as a string, do so now */
+ RETVAL_TRUE;
+
+ if (!tofile) {
+ char * bio_mem_ptr;
+ long bio_mem_len;
+
+ bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr);
+ ZVAL_STRINGL(out, bio_mem_ptr, bio_mem_len, 1);
+ }
+ }
+ }
+ PHP_SSL_REQ_DISPOSE(&req);
-clean_exit:
- if (certresource == 1 && cert)
- X509_free(cert);
- if (cainfo)
- X509_STORE_free(cainfo);
- if (untrustedchain)
- sk_X509_pop_free(untrustedchain, X509_free);
+ if (key_resource == -1 && key) {
+ EVP_PKEY_free(key);
+ }
+ if (bio_out)
+ BIO_free(bio_out);
}
/* }}} */
-/* {{{ proto int openssl_get_publickey(mixed cert)
+/* {{{ proto int openssl_pkey_get_public(mixed cert)
Get public key from X.509 certificate */
-PHP_FUNCTION(openssl_get_publickey)
+PHP_FUNCTION(openssl_pkey_get_public)
{
zval *cert;
EVP_PKEY *pkey;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &cert) == FAILURE)
return;
- return_value->type = IS_RESOURCE;
+ Z_TYPE_P(return_value) = IS_RESOURCE;
pkey = php_openssl_evp_from_zval(&cert, 1, NULL, 1, &Z_LVAL_P(return_value) TSRMLS_CC);
if (pkey == NULL) {
}
/* }}} */
-/* {{{ proto void openssl_free_key(int key)
+/* {{{ proto void openssl_pkey_free(int key)
Free key */
-PHP_FUNCTION(openssl_free_key)
+PHP_FUNCTION(openssl_pkey_free)
{
zval *key;
EVP_PKEY *pkey;
}
/* }}} */
+/* {{{ proto int openssl_pkey_get_private(string key [, string passphrase])
+ Get private key */
+PHP_FUNCTION(openssl_pkey_get_private)
+=======
/* {{{ proto resource openssl_x509_read(mixed cert)
Read X.509 certificate */
PHP_FUNCTION(openssl_x509_read)
return;
return_value->type = IS_RESOURCE;
- x509 = php_openssl_x509_from_zval(&cert, 1, &Z_LVAL_P(return_value) TSRMLS_CC);
-
- if (x509 == NULL) {
- zend_error(E_WARNING, "%s() supplied parameter cannot be coerced into an X509 certificate!", get_active_function_name(TSRMLS_C));
- RETURN_FALSE;
- }
-}
-/* }}} */
-
-/* {{{ proto void openssl_free_x509(resource x509)
- Free X.509 certificate */
-PHP_FUNCTION(openssl_x509_free)
-{
- zval *x509;
- X509 *cert;
-
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &x509) == FAILURE)
- return;
-
- ZEND_FETCH_RESOURCE(cert, X509 *, &x509, -1, "OpenSSL X.509", le_x509);
- zend_list_delete(Z_LVAL_P(x509));
-}
-/* }}} */
-
-/* {{{ 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.
-*/
-static X509_STORE * setup_verify(zval * calist TSRMLS_DC)
-{
- X509_STORE *store;
- X509_LOOKUP * dir_lookup, * file_lookup;
- HashPosition pos;
- int ndirs = 0, nfiles = 0;
-
- store = X509_STORE_new();
-
- if (store == NULL)
- return NULL;
-
- if (calist && (calist->type == IS_ARRAY)) {
- zend_hash_internal_pointer_reset_ex(HASH_OF(calist), &pos);
- for (;; zend_hash_move_forward_ex(HASH_OF(calist), &pos)) {
- zval ** item;
- struct stat sb;
-
- if (zend_hash_get_current_data_ex(HASH_OF(calist), (void**)&item, &pos) == FAILURE)
- break;
-
- convert_to_string_ex(item);
-
- if (VCWD_STAT(Z_STRVAL_PP(item), &sb) == -1) {
- zend_error(E_WARNING, "%s() unable to stat %s", get_active_function_name(TSRMLS_C), Z_STRVAL_PP(item));
- 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, Z_STRVAL_PP(item), X509_FILETYPE_PEM))
- zend_error(E_WARNING, "%s() error loading file %s", get_active_function_name(TSRMLS_C), Z_STRVAL_PP(item));
- 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, Z_STRVAL_PP(item), X509_FILETYPE_PEM))
- zend_error(E_WARNING, "%s() error loading directory %s", get_active_function_name(TSRMLS_C), Z_STRVAL_PP(item));
- else
- ndirs++;
- dir_lookup = NULL;
- }
- }
- }
- if (nfiles == 0) {
- file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
- if (file_lookup)
- X509_LOOKUP_load_file(file_lookup, NULL, X509_FILETYPE_DEFAULT);
- }
- if (ndirs == 0) {
- dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
- if (dir_lookup)
- X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT);
+ x509 = php_openssl_x509_from_zval(&cert, 1, &Z_LVAL_P(return_value) TSRMLS_CC);
+
+ if (x509 == NULL) {
+ zend_error(E_WARNING, "%s() supplied parameter cannot be coerced into an X509 certificate!", get_active_function_name(TSRMLS_C));
+ RETURN_FALSE;
}
- return store;
}
/* }}} */
-/* {{{ proto mixed openssl_error_string()
- returns a description of the last error, and alters the index of the error messages. returns false when the are no more messages. */
-PHP_FUNCTION(openssl_error_string)
+/* {{{ proto void openssl_free_x509(resource x509)
+ Free X.509 certificate */
+PHP_FUNCTION(openssl_x509_free)
{
- char buf[512];
- unsigned long val;
+ zval *x509;
+ X509 *cert;
- if (ZEND_NUM_ARGS() != 0) {
- WRONG_PARAM_COUNT;
- }
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &x509) == FAILURE)
+ return;
- val = ERR_get_error();
- if (val)
- {
- RETURN_STRING(ERR_error_string(val, buf), 1);
- }
- else
- {
+ ZEND_FETCH_RESOURCE(cert, X509 *, &x509, -1, "OpenSSL X.509", le_x509);
+ zend_list_delete(Z_LVAL_P(x509));
+}
+/* }}} */
+
+/* {{{ 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.
+*/
+static X509_STORE * setup_verify(zval * calist TSRMLS_DC)
+{
+{
+ EVP_PKEY *pkey;
+ zval * key;
+ char * passphrase = "";
+ int passphrase_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|s", &key, &passphrase, &passphrase_len) == FAILURE)
+ return;
+
+ Z_TYPE_P(return_value) = IS_RESOURCE;
+ pkey = php_openssl_evp_from_zval(&key, 0, passphrase, 1, &Z_LVAL_P(return_value) TSRMLS_CC);
+
+ if (pkey == NULL) {
+ zend_error(E_WARNING, "%s(): unable to coerce arg to a private key", get_active_function_name(TSRMLS_C));
RETURN_FALSE;
}
}
/* }}} */
+/* }}} */
+
+/* {{{ PKCS7 S/MIME functions */
+
/* {{{ proto bool openssl_pkcs7_verify(string filename, long flags[, string signerscerts][, array cainfo][, string extracerts])
verify that the data block is intact, the signer is who they say they are, and return the certs of the signers
*/
char * extracertsfilename = NULL; long extracertsfilename_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sszza!|ls",
- &infilename, &infilename_len, &outfilename, &outfilename_len,
+ &infilename, &infilename_len, *outfilename, &outfilename_len,
&zcert, &zprivkey, &zheaders, &flags, &extracertsfilename,
&extracertsfilename_len) == FAILURE)
return;
goto clean_exit;
}
- cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
- if (cert == NULL) {
- zend_error(E_WARNING, "%s(): error getting cert", get_active_function_name(TSRMLS_C));
- goto clean_exit;
+ cert = php_openssl_x509_from_zval(&zcert, 0, &certresource TSRMLS_CC);
+ if (cert == NULL) {
+ zend_error(E_WARNING, "%s(): error getting cert", get_active_function_name(TSRMLS_C));
+ goto clean_exit;
+ }
+
+ infile = BIO_new_file(infilename, "r");
+ if (infile == NULL) {
+ zend_error(E_WARNING, "%s(): error opening input file %s!", get_active_function_name(TSRMLS_C), infilename);
+ goto clean_exit;
+ }
+
+ outfile = BIO_new_file(outfilename, "w");
+ if (outfile == NULL) {
+ zend_error(E_WARNING, "%s(): error opening output file %s!", get_active_function_name(TSRMLS_C), outfilename);
+ goto clean_exit;
+ }
+
+ p7 = PKCS7_sign(cert, privkey, others, infile, flags);
+ if (p7 == NULL) {
+ zend_error(E_WARNING, "%s(): error creating PKCS7 structure!", get_active_function_name(TSRMLS_C));
+ goto clean_exit;
+ }
+
+ BIO_reset(infile);
+
+ /* tack on extra headers */
+ if (zheaders) {
+ zend_hash_internal_pointer_reset_ex(HASH_OF(zheaders), &hpos);
+ while(zend_hash_get_current_data_ex(HASH_OF(zheaders), (void**)&hval, &hpos) == SUCCESS) {
+ zend_hash_get_current_key_ex(HASH_OF(zheaders), &strindex, &strindexlen, &intindex, 0, &hpos);
+
+ convert_to_string_ex(hval);
+
+ if (strindex)
+ BIO_printf(outfile, "%s: %s\n", strindex, Z_STRVAL_PP(hval));
+ else
+ BIO_printf(outfile, "%s\n", Z_STRVAL_PP(hval));
+
+ zend_hash_move_forward_ex(HASH_OF(zheaders), &hpos);
+ }
+ }
+ /* write the signed data */
+ SMIME_write_PKCS7(outfile, p7, infile, flags);
+
+ RETVAL_TRUE;
+
+clean_exit:
+ PKCS7_free(p7);
+ BIO_free(infile);
+ BIO_free(outfile);
+ if (others)
+ sk_X509_pop_free(others, X509_free);
+ if (privkey && keyresource == -1)
+ EVP_PKEY_free(privkey);
+ if (cert && certresource == -1)
+ X509_free(cert);
+}
+/* }}} */
+
+/* {{{ proto bool openssl_pkcs7_decrypt(string infilename, string outfilename, mixed recipcert[, mixed recipkey])
+ decrypt the S/MIME message in the file name infilename and output the results to the file name outfilename. recipcert is a cert for one of the recipients. recipkey specifies the private key matching recipcert, if recipcert does not include the key */
+
+PHP_FUNCTION(openssl_pkcs7_decrypt)
+{
+ zval * recipcert, * recipkey = NULL;
+ X509 * cert = NULL;
+ EVP_PKEY * key = NULL;
+ long certresval, keyresval;
+ BIO * in = NULL, * out = NULL, * datain = NULL;
+ PKCS7 * p7 = NULL;
+ char * infilename; long infilename_len;
+ char * outfilename; long outfilename_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssz|z", &infilename, &infilename_len,
+ &outfilename, &outfilename_len, &recipcert, &recipkey) == FAILURE)
+ return;
+
+ RETVAL_FALSE;
+
+ cert = php_openssl_x509_from_zval(&recipcert, 0, &certresval TSRMLS_CC);
+ if (cert == NULL) {
+ zend_error(E_WARNING, "%s(): unable to coerce parameter 3 to x509 cert", get_active_function_name(TSRMLS_C));
+ goto clean_exit;
+ }
+
+ key = php_openssl_evp_from_zval(recipkey ? &recipkey : &recipcert, 0, "", 0, &keyresval TSRMLS_CC);
+ if (key == NULL) {
+ zend_error(E_WARNING, "%s(): unable to get private key", get_active_function_name(TSRMLS_C));
+ goto clean_exit;
+ }
+
+ in = BIO_new_file(infilename, "r");
+ if (in == NULL) {
+ goto clean_exit;
+ }
+ out = BIO_new_file(outfilename, "w");
+ if (out == NULL) {
+ goto clean_exit;
+ }
+
+ p7 = SMIME_read_PKCS7(in, &datain);
+
+ if (p7 == NULL)
+ goto clean_exit;
+
+ if (PKCS7_decrypt(p7, key, cert, out, PKCS7_DETACHED))
+ RETVAL_TRUE;
+
+clean_exit:
+ PKCS7_free(p7);
+ BIO_free(datain);
+ BIO_free(in);
+ BIO_free(out);
+ if (cert && certresval == -1)
+ X509_free(cert);
+ if (key && keyresval == -1)
+ EVP_PKEY_free(key);
+}
+/* }}} */
+
+/* }}} */
+
+/* {{{ proto bool openssl_private_encrypt(string data, string crypted, mixed key [, int padding])
+ Encrypt data with private key */
+PHP_FUNCTION(openssl_private_encrypt)
+{
+ zval *key, *crypted;
+ EVP_PKEY *pkey;
+ int cryptedlen;
+ unsigned char *cryptedbuf = NULL;
+ int successful = 0;
+ long keyresource = -1;
+ char * data;
+ long data_len, padding = RSA_PKCS1_PADDING;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE)
+ return;
+
+ RETVAL_FALSE;
+
+ pkey = php_openssl_evp_from_zval(&key, 0, "", 0, &keyresource TSRMLS_CC);
+
+ if (pkey == NULL) {
+ zend_error(E_WARNING, "%s(): key param is not a valid private key",
+ get_active_function_name(TSRMLS_C));
+ RETURN_FALSE;
+ }
+
+ cryptedlen = EVP_PKEY_size(pkey);
+ cryptedbuf = emalloc(cryptedlen + 1);
+
+ switch (pkey->type) {
+ case EVP_PKEY_RSA:
+ case EVP_PKEY_RSA2:
+ successful = (RSA_private_encrypt(data_len,
+ data,
+ cryptedbuf,
+ pkey->pkey.rsa,
+ padding) == cryptedlen);
+ break;
+ default:
+ zend_error(E_WARNING, "%s(): key type not supported in this PHP build!");
+ }
+
+ if (successful) {
+ zval_dtor(crypted);
+ cryptedbuf[cryptedlen] = '\0';
+ ZVAL_STRINGL(crypted, cryptedbuf, cryptedlen, 0);
+ cryptedbuf = NULL;
+ RETVAL_TRUE;
+ }
+ if (cryptedbuf)
+ efree(cryptedbuf);
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
+}
+/* }}} */
+
+/* {{{ proto bool openssl_private_decrypt(string data, string crypted, mixed key [, int padding])
+ Decrypt data with private key */
+PHP_FUNCTION(openssl_private_decrypt)
+{
+ zval *key, *crypted;
+ EVP_PKEY *pkey;
+ int cryptedlen;
+ unsigned char *cryptedbuf;
+ unsigned char *crypttemp;
+ int successful = 0;
+ long padding = RSA_PKCS1_PADDING;
+ long keyresource = -1;
+ char * data;
+ long data_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE)
+ return;
+
+ RETVAL_FALSE;
+
+ pkey = php_openssl_evp_from_zval(&key, 0, "", 0, &keyresource TSRMLS_CC);
+ if (pkey == NULL) {
+ zend_error(E_WARNING, "%s(): key parameter is not a valid private key", get_active_function_name(TSRMLS_C));
+ RETURN_FALSE;
+ }
+
+ cryptedlen = EVP_PKEY_size(pkey);
+ crypttemp = emalloc(cryptedlen + 1);
+
+ switch (pkey->type) {
+ case EVP_PKEY_RSA:
+ case EVP_PKEY_RSA2:
+ cryptedlen = RSA_private_decrypt(data_len,
+ data,
+ crypttemp,
+ pkey->pkey.rsa,
+ padding);
+ if (cryptedlen != -1) {
+ cryptedbuf = emalloc(cryptedlen + 1);
+ memcpy(cryptedbuf, crypttemp, cryptedlen);
+ successful = 1;
+ }
+ break;
+ default:
+ zend_error(E_WARNING, "%s(): key type not supported in this PHP build!");
}
- infile = BIO_new_file(infilename, "r");
- if (infile == NULL) {
- zend_error(E_WARNING, "%s(): error opening input file %s!", get_active_function_name(TSRMLS_C), infilename);
- goto clean_exit;
- }
+ efree(crypttemp);
- outfile = BIO_new_file(outfilename, "w");
- if (outfile == NULL) {
- zend_error(E_WARNING, "%s(): error opening output file %s!", get_active_function_name(TSRMLS_C), outfilename);
- goto clean_exit;
+ if (successful) {
+ zval_dtor(crypted);
+ cryptedbuf[cryptedlen] = '\0';
+ ZVAL_STRINGL(crypted, cryptedbuf, cryptedlen, 0);
+ cryptedbuf = NULL;
+ RETVAL_TRUE;
}
- p7 = PKCS7_sign(cert, privkey, others, infile, flags);
- if (p7 == NULL) {
- zend_error(E_WARNING, "%s(): error creating PKCS7 structure!", get_active_function_name(TSRMLS_C));
- goto clean_exit;
- }
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
+ if (cryptedbuf)
+ efree(cryptedbuf);
+}
+/* }}} */
- BIO_reset(infile);
+/* {{{ proto bool openssl_public_encrypt(string data, string crypted, mixed key [, int padding])
+ Encrypt data with public key */
+PHP_FUNCTION(openssl_public_encrypt)
+{
+ zval *key, *crypted;
+ EVP_PKEY *pkey;
+ int cryptedlen;
+ unsigned char *cryptedbuf;
+ int successful = 0;
+ long keyresource = -1;
+ long padding = RSA_PKCS1_PADDING;
+ char * data;
+ long data_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE)
+ return;
- /* tack on extra headers */
- if (zheaders) {
- zend_hash_internal_pointer_reset_ex(HASH_OF(zheaders), &hpos);
- while(zend_hash_get_current_data_ex(HASH_OF(zheaders), (void**)&hval, &hpos) == SUCCESS) {
- zend_hash_get_current_key_ex(HASH_OF(zheaders), &strindex, &strindexlen, &intindex, 0, &hpos);
+ RETVAL_FALSE;
+
+ pkey = php_openssl_evp_from_zval(&key, 1, NULL, 0, &keyresource TSRMLS_CC);
+ if (pkey == NULL) {
+ zend_error(E_WARNING, "%s(): key parameter is not a valid public key", get_active_function_name(TSRMLS_C));
+ RETURN_FALSE;
+ }
- convert_to_string_ex(hval);
+ cryptedlen = EVP_PKEY_size(pkey);
+ cryptedbuf = emalloc(cryptedlen + 1);
- if (strindex)
- BIO_printf(outfile, "%s: %s\n", strindex, Z_STRVAL_PP(hval));
- else
- BIO_printf(outfile, "%s\n", Z_STRVAL_PP(hval));
+ switch (pkey->type) {
+ case EVP_PKEY_RSA:
+ case EVP_PKEY_RSA2:
+ successful = (RSA_public_encrypt(data_len,
+ data,
+ cryptedbuf,
+ pkey->pkey.rsa,
+ padding) == cryptedlen);
+ break;
+ default:
+ zend_error(E_WARNING, "%s(): key type not supported in this PHP build!");
- zend_hash_move_forward_ex(HASH_OF(zheaders), &hpos);
- }
}
- /* write the signed data */
- SMIME_write_PKCS7(outfile, p7, infile, flags);
-
- RETVAL_TRUE;
-clean_exit:
- PKCS7_free(p7);
- BIO_free(infile);
- BIO_free(outfile);
- if (others)
- sk_X509_pop_free(others, X509_free);
- if (privkey && keyresource == -1)
- EVP_PKEY_free(privkey);
- if (cert && certresource == -1)
- X509_free(cert);
+ if (successful) {
+ zval_dtor(crypted);
+ cryptedbuf[cryptedlen] = '\0';
+ ZVAL_STRINGL(crypted, cryptedbuf, cryptedlen, 0);
+ cryptedbuf = NULL;
+ RETVAL_TRUE;
+ }
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
+ if (cryptedbuf)
+ efree(cryptedbuf);
}
/* }}} */
-/* {{{ proto bool openssl_pkcs7_decrypt(string infilename, string outfilename, mixed recipcert[, mixed recipkey])
- decrypt the S/MIME message in the file name infilename and output the results to the file name outfilename. recipcert is a cert for one of the recipients. recipkey specifies the private key matching recipcert, if recipcert does not include the key */
-
-PHP_FUNCTION(openssl_pkcs7_decrypt)
+/* {{{ proto bool openssl_public_decrypt(string data, string crypted, resource key [, int padding])
+ Decrypt data with public key */
+PHP_FUNCTION(openssl_public_decrypt)
{
- zval * recipcert, * recipkey = NULL;
- X509 * cert = NULL;
- EVP_PKEY * key = NULL;
- long certresval, keyresval;
- BIO * in = NULL, * out = NULL, * datain = NULL;
- PKCS7 * p7 = NULL;
- char * infilename; long infilename_len;
- char * outfilename; long outfilename_len;
+ zval *key, *crypted;
+ EVP_PKEY *pkey;
+ int cryptedlen;
+ unsigned char *cryptedbuf;
+ unsigned char *crypttemp;
+ int successful = 0;
+ long keyresource = -1;
+ long padding = RSA_PKCS1_PADDING;
+ char * data;
+ long data_len;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssz|z", &infilename, &infilename_len,
- &outfilename, &outfilename_len, &recipcert, &recipkey) == FAILURE)
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE)
return;
RETVAL_FALSE;
-
- cert = php_openssl_x509_from_zval(&recipcert, 0, &certresval TSRMLS_CC);
- if (cert == NULL) {
- zend_error(E_WARNING, "%s(): unable to coerce parameter 3 to x509 cert", get_active_function_name(TSRMLS_C));
- goto clean_exit;
+
+ pkey = php_openssl_evp_from_zval(&key, 1, NULL, 0, &keyresource TSRMLS_CC);
+ if (pkey == NULL) {
+ zend_error(E_WARNING, "%s(): key parameter is not a valid public key", get_active_function_name(TSRMLS_C));
+ RETURN_FALSE;
}
- key = php_openssl_evp_from_zval(recipkey ? &recipkey : &recipcert, 0, "", 0, &keyresval TSRMLS_CC);
- if (key == NULL) {
- zend_error(E_WARNING, "%s(): unable to get private key", get_active_function_name(TSRMLS_C));
- goto clean_exit;
- }
+ cryptedlen = EVP_PKEY_size(pkey);
+ crypttemp = emalloc(cryptedlen + 1);
- in = BIO_new_file(infilename, "r");
- if (in == NULL) {
- goto clean_exit;
+ switch (pkey->type) {
+ case EVP_PKEY_RSA:
+ case EVP_PKEY_RSA2:
+ cryptedlen = RSA_public_decrypt(data_len,
+ data,
+ crypttemp,
+ pkey->pkey.rsa,
+ padding);
+ if (cryptedlen != -1) {
+ cryptedbuf = emalloc(cryptedlen + 1);
+ memcpy(cryptedbuf, crypttemp, cryptedlen);
+ successful = 1;
+ }
+ break;
+
+ default:
+ zend_error(E_WARNING, "%s(): key type not supported in this PHP build!");
+
}
- out = BIO_new_file(outfilename, "w");
- if (out == NULL) {
- goto clean_exit;
+
+ efree(crypttemp);
+
+ if (successful) {
+ zval_dtor(crypted);
+ cryptedbuf[cryptedlen] = '\0';
+ ZVAL_STRINGL(crypted, cryptedbuf, cryptedlen, 0);
+ cryptedbuf = NULL;
+ RETVAL_TRUE;
}
- p7 = SMIME_read_PKCS7(in, &datain);
+ if (cryptedbuf)
+ efree(cryptedbuf);
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
+}
+/* }}} */
- if (p7 == NULL)
- goto clean_exit;
+/* {{{ proto mixed openssl_error_string()
+ returns a description of the last error, and alters the index of the error messages. returns false when the are no more messages. */
+PHP_FUNCTION(openssl_error_string)
+{
+ char buf[512];
+ unsigned long val;
- if (PKCS7_decrypt(p7, key, cert, out, PKCS7_DETACHED))
- RETVAL_TRUE;
+ if (ZEND_NUM_ARGS() != 0) {
+ WRONG_PARAM_COUNT;
+ }
-clean_exit:
- PKCS7_free(p7);
- BIO_free(datain);
- BIO_free(in);
- BIO_free(out);
- if (cert && certresval == -1)
- X509_free(cert);
- if (key && keyresval == -1)
- EVP_PKEY_free(key);
+ val = ERR_get_error();
+ if (val)
+ {
+ RETURN_STRING(ERR_error_string(val, buf), 1);
+ }
+ else
+ {
+ RETURN_FALSE;
+ }
}
/* }}} */
}
/* }}} */
-/* {{{ _php_pkey_free() */
-static void _php_pkey_free(zend_rsrc_list_entry *rsrc TSRMLS_DC)
-{
- EVP_PKEY *pkey = (EVP_PKEY *)rsrc->ptr;
- EVP_PKEY_free(pkey);
-}
-/* }}} */
-
-/* {{{ _php_x509_free() */
-static void _php_x509_free(zend_rsrc_list_entry *rsrc TSRMLS_DC)
-{
- X509 *x509 = (X509 *)rsrc->ptr;
- X509_free(x509);
-}
-/* }}} */
-
/*
* Local variables:
* tab-width: 8