wez Sun Apr 1 16:06:14 2001 EDT Modified files: /php4/ext/openssl CREDITS README openssl.c php_openssl.h Log: Added whole bunch of PKCS7 (S/MIME) functions and made the key/cert params a bit more friendly to use. See the README for more info.
Index: php4/ext/openssl/CREDITS diff -u php4/ext/openssl/CREDITS:1.2 php4/ext/openssl/CREDITS:1.3 --- php4/ext/openssl/CREDITS:1.2 Thu Nov 23 11:30:37 2000 +++ php4/ext/openssl/CREDITS Sun Apr 1 16:06:14 2001 @@ -1,2 +1,2 @@ OpenSSL -Stig Venaas +Stig Venaas, Wez Furlong Index: php4/ext/openssl/README diff -u php4/ext/openssl/README:1.1 php4/ext/openssl/README:1.2 --- php4/ext/openssl/README:1.1 Thu Nov 23 11:30:37 2000 +++ php4/ext/openssl/README Sun Apr 1 16:06:14 2001 @@ -1,53 +1,210 @@ OpenSSL extension for PHP4 -$Id: README,v 1.1 2000/11/23 19:30:37 venaas Exp $ +$Id: README,v 1.2 2001/04/01 23:06:14 wez Exp $ -The functions implemented so far make it possible to seal and open data, -and also create and verify signatures. To enable the extension, configure -PHP with --with-openssl. +The functions implemented so far make it possible to seal and open data, and +also create and verify signatures. +NEW: support for S/MIME encrypt/decrypt/sign/verify, as well as more +flexibility for specifying certificates/keys. -Functions: +To enable the extension, configure PHP with --with-openssl. -int openssl_get_privatekey(string key [, string passphrase]) +Specifying keys/certificates +---------------------------- +Most of the functions require a key or a certificate as a parameter; to make +things easy for you to use openssl, this extension allows you +to specify certificates in the following way: + +1. As an X.509 resource returned from openssl_x509_read +2. As a string in the format file://filename, where filename is the path to the + certificate file (it will be opened and read automatically) +3. As a string containing the data from the certificate file + +Similarly, you can use the following methods of specifying a public key: + +1. As a key resource returned from openssl_get_publickey +2. An X509 resource - public key only +3. As a string in the format file://filename +4. As a string containing the data from the key file + +Additionally, for a private key, when the openssl extension function does not +allow you to enter the passphrase as a parameter you may use the syntax +array($key, "passphrase") where $key can be a key specified using one of the +methods listed above. + +Certificate Verification +------------------------ +When calling a function that will verify a signature/certificate, the cainfo +parameter is an array containing file and directory names that specifiy the +locations of trusted CA files. If a directory is specified, then it must be a +correctly hashed directory. + +Misc: +----- + +mixed openssl_error_string() + +returns the message from the last error that the OpenSSL library encountered +and moves it's internal error pointer to the next message. If there are no +more error messages, returns false. + +General Key/Cert Functions: +--------------------------- + +resource openssl_get_privatekey(mixed key [, string passphrase]) + Parses the key data and returns a key resource identifier. If the key is encrypted a passphrase is needed. This can be supplied as second argument. -int openssl_get_publickey(string cert) +resource openssl_get_publickey(mixed cert) Extracts the public key from the given certificate and returns a key resource identifier. -void openssl_free_key(int key) +void openssl_free_key(resource key) Frees the resource given by the key resource identifier. +Note that this function does not accept the extended key specification +syntax mentioned above, as it doesn't make sense in this case! + +array openssl_x509_parse(mixed x509[, bool shortnames=true]) + +Parses the certificate data and returns an array containing information +about the certificate, it's intended purposes, subject, issuer, validity +etc. etc. If shortnames is true (the default) then the fields will be +keyed by the shortname forms eg: CN as opposed to commonName (shortnames += false). + + +bool openssl_x509_checkpurpose(mixed x509cert, int purpose, + array cainfo[, string untrustedfile]) + +Verifies if the certificate can be used for a specific purpose. +Purpose can be one of the following values: + X509_PURPOSE_SSL_CLIENT + X509_PURPOSE_SSL_SERVER + X509_PURPOSE_NS_SSL_SERVER + X509_PURPOSE_SMIME_SIGN + X509_PURPOSE_SMIME_ENCRYPT + X509_PURPOSE_CRL_SIGN + X509_PURPOSE_ANY + +cainfo is an array of CA information (as mentioned above). +untrusted file specifies a file containing a bunch of certs that +are not trusted but may be useful in validating the certificate. + + +resource openssl_read_x509(mixed cert) + +Parses the cert and returns a resource that can be used with the +other openssl functions + + +void openssl_free_x509(resource x509) + +Frees the resource given by the x509 resource identifier. +Note that this function does not accept the extended cert specification +syntax mentioned above, as it doesn't make sense in this case! + + +PKCS7 (S/MIME) Sign/Verify/Encrypt/Decrypt Functions: +----------------------------------------------------- + +These functions allow you to manipulate S/MIME messages! + +They are based on apps/smime.c from the openssl dist, so for information, +see the documentation for openssl. + +You may pass in some flags that affect how these functions work using +and array containing the following values: +"detached", "nodetached", "text", "nointern", "noverify", "nochain", +"nocerts", "noattr", "binary", "nosigs". +The options correspond to the options of the same name for the +"openssl smime" command (smime(1)). + + +bool openssl_pkcs7_verify(string filename, array flags[, string signerscerts][, + array cainfo]) + +Verifies that the signature on the MIME message contained in the file +named by filename is valid. If signerscerts is passed in, it holds the +name of a file into which the certificates of those that signed the +message will be stored. +cainfo and flags are CA information and flag information as described +above. + + +bool openssl_pkcs7_encrypt(string infile, string outfile, array recipcerts, + array headers[, array flags]) + +Encrypts the MIME message contained in the file named by infile using +the certificates held in recipcerts. The result is place in the file +named outfile. +recipcerts is an array of certificate identifiers representing the certs +of the intended recipients of the message. +headers is an array of headers to prepend to the message: they will +not be included in the encoded section. +flags is flag information as described above. +Hint: you will want to put "To", "From", and "Subject" headers in headers. +Headers can be either an assoc array keyed by header named, or can be +and indexed array containing a single header line per value. +The message will be encoded using a RC2-40 bit cipher. +TODO: allow user to specify cipher. + +bool openssl_pkcs7_sign(string infile, string outfile, mixed signcert, mixed + signkey, array headers[, array flags][, string extracertsfilename]) + +Signs the MIME message contained in the file named by infile using the +certificate and key pair identified by signcert/signkey. +Signkey must be the private key corresponding to signcert. +The result is placed in the file named by outfile. +Headers and flags have the same effects as mentioned above. +extracertsfilename names a file containing a bunch of additional certificates +to include in the signature, in order to aid the recipient in verifying the +message. + + +bool openssl_pkcs7_decrypt(string infilename, string outfilename, mixed + recipcert, mixed recipkey) + +Decrypts the MIME message contained in the file named by infilename +using the certificate and private key pair recipcert/recipkey. +The descrypted result is placed in outfilename. +TODO: add flags parameter, if needed? + +EVP Sign/Verify/Encrypt/Decrypt Functions: +------------------------------------------ -bool openssl_sign(string data, string signature, int key) +bool openssl_sign(string data, &string signature, mixed key) Uses key to create signature for data, returns true on success and false -on failure. +on failure. signature is passed by reference and contains the newly created +signature on success. -int openssl_verify(string data, string signature, int key) +int openssl_verify(string data, string signature, mixed key) Uses key to verify that the signature is correct for the given data. Returns 1 if correct, 0 if incorrect, and -1 on error. -int openssl_seal(string data, string sealdata, array ekeys, array pubkeys) +int openssl_seal(string data, &string sealdata, &array ekeys, array pubkeys) Encrypts data using pubkeys, so that only owners of the respective private keys and ekeys can decrypt and read the data. Returns the length of the -sealed data on success, else false. +sealed data on success, else false. On success, sealdata and ekeys hold +the sealed data and envelope keys. -bool openssl_open(string data, string opendata, string ekey, int privkey) +bool openssl_open(string data, &string opendata, string ekey, int privkey) Opens (decrypts) sealed data using a private key and the corresponding -envelope key. Returns true on success and false on failure. +envelope key. Returns true on success and false on failure. On success, +opendata will hold the descypted data. See below for more details on usage. Also feel free to mail me at Index: php4/ext/openssl/openssl.c diff -u php4/ext/openssl/openssl.c:1.9 php4/ext/openssl/openssl.c:1.10 --- php4/ext/openssl/openssl.c:1.9 Sun Feb 25 22:07:10 2001 +++ php4/ext/openssl/openssl.c Sun Apr 1 16:06:14 2001 @@ -13,11 +13,12 @@ | [EMAIL PROTECTED] so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Stig Venaas <[EMAIL PROTECTED]> | + | Wez Furlong <[EMAIL PROTECTED]> | +----------------------------------------------------------------------+ */ - -/* $Id: openssl.c,v 1.9 2001/02/26 06:07:10 andi Exp $ */ +/* $Id: openssl.c,v 1.10 2001/04/01 23:06:14 wez Exp $ */ + #include "php.h" #include "php_openssl.h" @@ -28,7 +29,10 @@ /* OpenSSL includes */ #include <openssl/evp.h> #include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/crypto.h> #include <openssl/pem.h> +#include <openssl/err.h> static unsigned char arg2of3_force_ref[] = { 3, BYREF_NONE, BYREF_FORCE, BYREF_NONE }; @@ -37,20 +41,29 @@ static unsigned char arg2and3of4_force_ref[] = { 4, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_NONE }; - function_entry openssl_functions[] = { PHP_FE(openssl_get_privatekey, NULL) PHP_FE(openssl_get_publickey, NULL) PHP_FE(openssl_free_key, NULL) -#if 0 - PHP_FE(openssl_read_publickey, NULL) - PHP_FE(openssl_read_x509, NULL) - PHP_FE(openssl_free_x509, NULL) -#endif + + PHP_FE(openssl_x509_read, NULL) + PHP_FE(openssl_x509_free, NULL) + + PHP_FE(openssl_x509_parse, NULL) + PHP_FE(openssl_x509_checkpurpose, 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) + PHP_FE(openssl_pkcs7_sign, NULL) + PHP_FE(openssl_pkcs7_encrypt, NULL) + +/* useful to figure out whats going on! */ + PHP_FE(openssl_error_string, NULL) {NULL, NULL, NULL} }; @@ -58,8 +71,8 @@ "openssl", openssl_functions, PHP_MINIT(openssl), + NULL, NULL, - NULL, NULL, PHP_MINFO(openssl), STANDARD_MODULE_PROPERTIES @@ -72,22 +85,39 @@ static void _php_pkey_free(zend_rsrc_list_entry *rsrc); static int le_key; -#if 0 static void _php_x509_free(zend_rsrc_list_entry *rsrc); static int le_x509; -#endif +static X509 * php_openssl_x509_from_zval(zval ** val, int makeresource, long * +resourceval); +static EVP_PKEY * php_openssl_evp_from_zval(zval ** val, int public_key, char * +passphrase, int makeresource, long * resourceval); +static unsigned long php_openssl_parse_pkcs7_flags(zval ** val, unsigned long def); +static X509_STORE * setup_verify(zval * calist); +static STACK_OF(X509) * load_all_certs_from_file(char *certfile); + + PHP_MINIT_FUNCTION(openssl) { - le_key = zend_register_list_destructors_ex(_php_pkey_free, NULL, - "OpenSSL key", - module_number); -#if 0 - le_x509 = zend_register_list_destructors_ex(_php_x509_free, NULL, - "OpenSSL X.509", - module_number); -#endif + 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); + OpenSSL_add_all_ciphers(); + +/* + SSL_load_error_strings(); +*/ + ERR_load_ERR_strings(); + ERR_load_crypto_strings(); + ERR_load_EVP_strings(); + + /* purposes for cert purpose checking */ + REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_CLIENT", X509_PURPOSE_SSL_CLIENT, +CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_SERVER", X509_PURPOSE_SSL_SERVER, +CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_NS_SSL_SERVER", +X509_PURPOSE_NS_SSL_SERVER, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SMIME_SIGN", X509_PURPOSE_SMIME_SIGN, +CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SMIME_ENCRYPT", +X509_PURPOSE_SMIME_ENCRYPT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_CRL_SIGN", X509_PURPOSE_CRL_SIGN, +CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_ANY", X509_PURPOSE_ANY, +CONST_CS|CONST_PERSISTENT); + return SUCCESS; } @@ -105,84 +135,557 @@ return SUCCESS; } + +/* + Given a zval, coerce it into an X509 object. + The zval can be: + . X509 resource created using openssl_read_x509() + . if it starts with file:// then it will be interpreted as the path to +that cert + . it will be interpreted as the cert data + If you supply makeresource, the result will be registered as an x509 resource +and + it's value returned in makeresource. +*/ +static X509 * php_openssl_x509_from_zval(zval ** val, int makeresource, long * +resourceval) +{ + X509 *cert = NULL; + + if (resourceval) + *resourceval = -1; + + if ((*val)->type == IS_RESOURCE) { + /* is it an x509 resource ? */ + void * what; + int type; + + what = zend_fetch_resource(val, -1, "OpenSSL X.509", &type, 1, +le_x509); + if (!what) + return NULL; + + /* this is so callers can decide if they should free the X509 */ + if (resourceval) + *resourceval = Z_LVAL_PP(val); + + if (type == le_x509) + return (X509*)what; + + /* other types could be used here - eg: file pointers and read in the +data from them */ + + return NULL; + } + /* 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) + { + /* read cert from the named file */ + BIO *in; + + in = BIO_new_file(Z_STRVAL_PP(val) + 7, "r"); + if (in == NULL) + return NULL; + cert = PEM_read_bio_X509(in, NULL, NULL, NULL); + BIO_free(in); + + } + else { + BIO *in; + + in = BIO_new_mem_buf(Z_STRVAL_PP(val), Z_STRLEN_PP(val)); + if (in == NULL) + return NULL; + + cert = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509, + PEM_STRING_X509, in, + NULL, NULL, NULL); + BIO_free(in); + } + + if (cert && makeresource && resourceval) { + *resourceval = zend_list_insert(cert, le_x509); + } + return cert; +} + + + +/* Given a zval, coerce it into a EVP_PKEY object. + It can be: + 1. private key resource from openssl_get_privatekey() + 2. X509 resource -> private 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) +{ + EVP_PKEY * key = NULL; + X509 * cert = NULL; + int free_cert = 0; + long cert_res = -1; + char * filename = NULL; + + if (resourceval) + *resourceval = -1; + + if ((*val)->type == IS_ARRAY) { + zval ** zphrase; + + /* get passphrase */ + + if (zend_hash_index_find(HASH_OF(*val), 1, (void **)&zphrase) == +FAILURE) { + zend_error(E_ERROR, "%s(): key array must be of the form +array(0 => key, 1 => phrase)", get_active_function_name()); + return NULL; + } + convert_to_string_ex(zphrase); + passphrase = Z_STRVAL_PP(zphrase); + + /* 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_ERROR, "%s(): key array must be of the form +array(0 => key, 1 => phrase)", get_active_function_name()); + return NULL; + } + } + + if ((*val)->type == IS_RESOURCE) { + void * what; + int type; + + what = zend_fetch_resource(val, -1, "OpenSSL X.509/key", &type, 2, +le_x509, le_key); + if (!what) + return NULL; + + if (resourceval) + *resourceval = Z_LVAL_PP(val); + + 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 */ + + return NULL; + } + 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; + + /* 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); + 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; + + key = (EVP_PKEY *) PEM_ASN1_read_bio((char +*(*)())d2i_PrivateKey, + PEM_STRING_EVP_PKEY, b, + NULL, NULL, passphrase); + BIO_free(b); + } + } + } + + if (public_key && cert && key == NULL) { + /* extract public key from X509 cert */ + key = (EVP_PKEY *) X509_get_pubkey(cert); + } + + if (free_cert && cert) + X509_free(cert); + + if (key && makeresource && resourceval) { + *resourceval = zend_list_insert(key, le_key); + } + return key; +} + + /* {{{ proto int openssl_get_privatekey(string key [, string passphrase]) Get private key */ PHP_FUNCTION(openssl_get_privatekey) { zval **key, **passphrase; - BIO *b; EVP_PKEY *pkey; int argc; - + argc = ZEND_NUM_ARGS(); if (argc < 1 || argc > 2 || - zend_get_parameters_ex(argc, &key, &passphrase) == FAILURE) { + zend_get_parameters_ex(argc, &key, &passphrase) == FAILURE) { WRONG_PARAM_COUNT; } convert_to_string_ex(key); if (argc == 2) { - convert_to_string_ex(passphrase); - } + convert_to_string_ex(passphrase); + } + + return_value->type = IS_RESOURCE; + pkey = php_openssl_evp_from_zval(key, 0, argc == 2 ? Z_STRVAL_PP(passphrase) : +"", 1, &(return_value->value.lval)); - b = BIO_new_mem_buf(Z_STRVAL_PP(key), Z_STRLEN_PP(key)); - if (b == NULL) { + if (pkey == NULL) { + zend_error(E_ERROR, "%s(): unable to coerce arg to a private key", +get_active_function_name()); RETURN_FALSE; } +} +/* }}} */ - pkey = (EVP_PKEY *) PEM_ASN1_read_bio((char *(*)())d2i_PrivateKey, - PEM_STRING_EVP_PKEY, b, - NULL, NULL, argc == 2 ? - Z_STRVAL_PP(passphrase) : NULL); - BIO_free(b); +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) +{ +/* + 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(), 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 = -(is_dst ? timezone - 3600 : timezone + 3600); +#endif + ret += gmadjust; + + efree(strbuf); + + return ret; +} + +/* 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, ** zshort = NULL; + X509 * cert = NULL; + long certresource = -1; + int i; + int argc = ZEND_NUM_ARGS(); + int useshortnames = 1; + char * tmpstr; + zval * subitem; - if (pkey == NULL) { + if (argc < 1 || argc > 2 || zend_get_parameters_ex(argc, &zcert, &zshort) == +FAILURE) { + WRONG_PARAM_COUNT; + } + if (argc == 2) { + convert_to_boolean_ex(zshort); + if (!Z_LVAL_PP(zshort)) + useshortnames = 0; + } + cert = php_openssl_x509_from_zval(zcert, 0, &certresource); + 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))); + add_assoc_long(return_value, "validTo_time_t", +asn1_time_to_time_t(X509_get_notAfter(cert))); + + 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 ? */ + + zend_hash_index_update(HASH_OF(subitem), id, (void *)&subsub, +sizeof(subsub), NULL); } + zend_hash_update(HASH_OF(return_value), "purposes", strlen("purposes")+1, +(void*)&subitem, sizeof(subitem), NULL); + + if (certresource == -1 && cert) + X509_free(cert); + +} +/* }}} */ + + +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; + + if(!(stack = sk_X509_new_null())) { + zend_error(E_ERROR, "%s(): memory allocation failure", +get_active_function_name()); + goto end; + } + + if(!(in=BIO_new_file(certfile, "r"))) { + zend_error(E_ERROR, "%s(): error opening the file, %s", +get_active_function_name(), 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_ERROR, "%s(): error reading the file, %s", +get_active_function_name(), 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_ERROR, "%s(): no certificates in file, %s", +get_active_function_name(), certfile); + sk_X509_free(stack); + goto end; + } + ret=stack; +end: + BIO_free(in); + sk_X509_INFO_free(sk); + + return ret; +} + +static int 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) + { + zend_error(E_ERROR, "%s(): memory allocation failure", +get_active_function_name()); + return 0; + } + 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; +} + + +/* {{{ proto bool 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, ** zpurpose, ** zcainfo, ** zuntrusted; + X509_STORE * cainfo = NULL; + X509 * cert = NULL; + long certresource = -1; + STACK_OF(X509) * untrustedchain = NULL; + int argc; + + argc = ZEND_NUM_ARGS(); + + if (argc < 3 || argc > 4 || zend_get_parameters_ex(argc, &zcert, &zpurpose, +&zcainfo, &zuntrusted) == FAILURE) { + WRONG_PARAM_COUNT; + } + + RETVAL_FALSE; - ZEND_REGISTER_RESOURCE(return_value, pkey, le_key); + if (argc == 4) { + convert_to_string_ex(zuntrusted); + untrustedchain = load_all_certs_from_file(Z_STRVAL_PP(zuntrusted)); + if (untrustedchain == NULL) + goto clean_exit; + } + convert_to_long_ex(zpurpose); + + cainfo = setup_verify(*zcainfo); + if (cainfo == NULL) + goto clean_exit; + + cert = php_openssl_x509_from_zval(zcert, 0, &certresource); + if (cert == NULL) + goto clean_exit; + + RETVAL_BOOL(check_cert(cainfo, cert, untrustedchain, Z_LVAL_PP(zpurpose))); + +clean_exit: + if (certresource == 1 && cert) + X509_free(cert); + if (cainfo) + X509_STORE_free(cainfo); + if (untrustedchain) + sk_X509_pop_free(untrustedchain, X509_free); } /* }}} */ -/* {{{ proto int openssl_get_publickey(string cert) + +/* {{{ proto int openssl_get_publickey(mixed cert) Get public key from X.509 certificate */ PHP_FUNCTION(openssl_get_publickey) { zval **cert; - X509 *x509; - BIO *b; EVP_PKEY *pkey; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &cert) == FAILURE) { WRONG_PARAM_COUNT; } - convert_to_string_ex(cert); - - b = BIO_new_mem_buf(Z_STRVAL_PP(cert), -1); - if (b == NULL) { - RETURN_FALSE; - } - - x509 = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509, - PEM_STRING_X509, b, - NULL, NULL, NULL); - BIO_free(b); - - if (x509 == NULL) { - RETURN_FALSE; - } - pkey = (EVP_PKEY *) X509_get_pubkey(x509); - X509_free(x509); + return_value->type = IS_RESOURCE; + pkey = php_openssl_evp_from_zval(cert, 1, NULL, 1, +&(return_value->value.lval)); - if (pkey == NULL) { + if (pkey == NULL) { RETURN_FALSE; } - - ZEND_REGISTER_RESOURCE(return_value, pkey, le_key); } /* }}} */ + + /* {{{ proto void openssl_free_key(int key) Free key */ PHP_FUNCTION(openssl_free_key) @@ -194,86 +697,577 @@ zend_get_parameters_ex(1, &key) == FAILURE) { WRONG_PARAM_COUNT; } - + ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, key, -1, "OpenSSL key", le_key); zend_list_delete(Z_LVAL_PP(key)); } /* }}} */ -#if 0 -/* {{{ proto int openssl_read_publickey(int x509) - Read public key */ -PHP_FUNCTION(openssl_read_publickey) +/* {{{ proto resource openssl_read_x509(mixed cert) + Read X.509 certificate */ +PHP_FUNCTION(openssl_x509_read) +{ + zval **cert; + X509 *x509; + + if (ZEND_NUM_ARGS() != 1 || + zend_get_parameters_ex(1, &cert) == FAILURE) { + WRONG_PARAM_COUNT; + } + + return_value->type = IS_RESOURCE; + x509 = php_openssl_x509_from_zval(cert, 1, &(return_value->value.lval)); + + if (x509 == NULL) { + zend_error(E_ERROR, "%s() supplied parameter cannot be coerced into an +X509 certificate!", get_active_function_name()); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto void openssl_free_x509(resource x509) + Free X.509 certificate */ +PHP_FUNCTION(openssl_x509_free) { zval **x509; X509 *cert; - EVP_PKEY *pkey; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &x509) == FAILURE) { WRONG_PARAM_COUNT; } ZEND_FETCH_RESOURCE(cert, X509 *, x509, -1, "OpenSSL X.509", le_x509); + zend_list_delete(Z_LVAL_PP(x509)); +} +/* }}} */ - pkey = (EVP_PKEY *) X509_get_pubkey(cert); - if (pkey == NULL) { - RETURN_FALSE; +static struct { + const char * name; + unsigned long value; + int or; +} php_pkcs7_option_values[] = { + {"detached", PKCS7_DETACHED, 1}, + {"nodetached", ~PKCS7_DETACHED, 0}, + {"text", PKCS7_TEXT, 1}, + {"nointern", PKCS7_NOINTERN, 1}, + {"noverify", PKCS7_NOVERIFY, 1}, + {"nochain", PKCS7_NOCHAIN, 1}, + {"nocerts", PKCS7_NOCERTS, 1}, + {"noattr", PKCS7_NOATTR, 1}, + {"binary", PKCS7_BINARY, 1}, + {"nosigs", PKCS7_NOSIGS, 1}, + {NULL, 0, 0} + +}; + +/* given an array of named flags, parse it into a form we can pass to the pkcs7 +routines */ +static unsigned long php_openssl_parse_pkcs7_flags(zval ** val, unsigned long def) +{ + HashPosition pos; + + if ((*val)->type == IS_NULL) + return def; + + if ((*val)->type != IS_ARRAY) + { + zend_error(E_WARNING, "%s(): expected an array of flag names", +get_active_function_name()); + return def; + } + + zend_hash_internal_pointer_reset_ex(HASH_OF(*val), &pos); + for (;; zend_hash_move_forward_ex(HASH_OF(*val), &pos)) { + zval ** item; + int i; + + if (zend_hash_get_current_data_ex(HASH_OF(*val), (void**)&item, &pos) +== FAILURE) + break; + + convert_to_string_ex(item); + + i = 0; + while (php_pkcs7_option_values[i].name) { + if (strcmp(php_pkcs7_option_values[i].name, Z_STRVAL_PP(item)) +== 0) { + if (php_pkcs7_option_values[i].or) + def |= php_pkcs7_option_values[i].value; + else + def &= php_pkcs7_option_values[i].value; + break; + } + i++; + } } - ZEND_REGISTER_RESOURCE(return_value, pkey, le_key); + return def; } -/* }}} */ -/* {{{ proto int openssl_read_x509(string cert) - Read X.509 certificate */ -PHP_FUNCTION(openssl_read_x509) +/* 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) { - zval **cert; - X509 *x509; - BIO *b; + 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 (V_STAT(Z_STRVAL_PP(item), &sb) == -1) { + zend_error(E_WARNING, "%s() unable to stat %s", +get_active_function_name(), Z_STRVAL_PP(item)); + continue; + } - if (ZEND_NUM_ARGS() != 1 || - zend_get_parameters_ex(1, &cert) == FAILURE) { + 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(), 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(), 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 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) +{ + char buf[512]; + unsigned long val; + + if (ZEND_NUM_ARGS() != 0) { WRONG_PARAM_COUNT; } - convert_to_string_ex(cert); - b = BIO_new_mem_buf(Z_STRVAL_PP(cert), -1); - if (b == NULL) { + val = ERR_get_error(); + if (val) + { + RETURN_STRING(ERR_error_string(val, buf), 1); + } + else + { RETURN_FALSE; } +} +/* }}} */ - x509 = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509, - PEM_STRING_X509, b, - NULL, NULL, NULL); - BIO_free(b); +/* {{{ proto bool openssl_pkcs7_verify(string filename, array flags[, string +signerscerts][, array cainfo]) + verify that the data block is intact, the signer is who they say they are, and +return the certs of the signers +*/ +PHP_FUNCTION(openssl_pkcs7_verify) +{ + X509_STORE * store; + int argc = ZEND_NUM_ARGS(); + zval ** data, ** zflags, ** signerscerts, ** cainfo = NULL; + char * signersfilename = NULL; + STACK_OF(X509) *signers; + PKCS7 * p7 = NULL; + BIO * in = NULL, * datain = NULL; + int flags = 0; + + if (argc > 4 || argc < 1) { + WRONG_PARAM_COUNT; + } - if (x509 == NULL) { + if (zend_get_parameters_ex(argc, &data, &zflags, &signerscerts, &cainfo) == +FAILURE) { + WRONG_PARAM_COUNT; + } + + if (argc >= 4) { + if ((*cainfo)->type != IS_ARRAY) { + zend_error(E_ERROR, "%s(): 4th parameter must be an array", +get_active_function_name()); + RETURN_FALSE; + } + } + if (argc >= 3) { + convert_to_string_ex(signerscerts); + signersfilename = Z_STRVAL_PP(signerscerts); + } + + convert_to_string_ex(data); + + flags = php_openssl_parse_pkcs7_flags(zflags, 0); + + store = setup_verify(cainfo ? *cainfo : NULL); + + if (!store) RETURN_FALSE; + + in = BIO_new_file(Z_STRVAL_PP(data), "r"); + if (in == NULL) + goto exit_fail; + p7 = SMIME_read_PKCS7(in, &datain); + if (p7 == NULL) + goto exit_fail; + + if (PKCS7_verify(p7, NULL, store, datain, NULL, flags)) { + + RETVAL_TRUE; + + if (signersfilename) { + BIO * certout = BIO_new_file(signersfilename, "w"); + if (certout) { + int i; + signers = PKCS7_get0_signers(p7, NULL, flags); + + for(i = 0; i < sk_X509_num(signers); i++) + PEM_write_bio_X509(certout, +sk_X509_value(signers, i)); + + BIO_free(certout); + sk_X509_free(signers); + } + } + + goto exit_cleanup; } - - ZEND_REGISTER_RESOURCE(return_value, x509, le_x509); + +exit_fail: + RETVAL_FALSE; +exit_cleanup: + X509_STORE_free(store); + BIO_free(datain); + BIO_free(in); + PKCS7_free(p7); } /* }}} */ -/* {{{ proto void openssl_free_x509(int x509) - Free X.509 certificate */ -PHP_FUNCTION(openssl_free_x509) +/* {{{ proto bool openssl_pkcs7_encrypt(string infile, string outfile, array +recipcerts, array headers[, array flags]) + encrypt the message in the file named infile with the certificates in +recipcerts and output the result to the file named outfile */ +PHP_FUNCTION(openssl_pkcs7_encrypt) { - zval **x509; - X509 *cert; + zval ** zinfilename, ** zoutfilename, ** zrecipcerts, ** zheaders, ** zflags = +NULL; + STACK_OF(X509) * recipcerts = NULL; + BIO * infile = NULL, * outfile = NULL; + int flags = 0; + PKCS7 * p7 = NULL; + HashPosition hpos; + zval ** zcertval; + X509 * cert; + int argc; + EVP_CIPHER *cipher = NULL; + ulong strindexlen, intindex; + char * strindex; - if (ZEND_NUM_ARGS() != 1 || - zend_get_parameters_ex(1, &x509) == FAILURE) { + argc = ZEND_NUM_ARGS(); + + RETVAL_FALSE; + + if (argc < 3 || argc > 5 || zend_get_parameters_ex(argc, &zinfilename, +&zoutfilename, &zrecipcerts, &zheaders, &zflags) == FAILURE) { WRONG_PARAM_COUNT; } - ZEND_FETCH_RESOURCE(cert, X509 *, x509, -1, "OpenSSL X.509", le_x509); - zend_list_delete(Z_LVAL_PP(x509)); + convert_to_string_ex(zinfilename); + convert_to_string_ex(zoutfilename); + + if (argc >= 4) { + if ((*zheaders)->type == IS_NULL) + zheaders = NULL; + else if ((*zheaders)->type != IS_ARRAY) { + zend_error(E_ERROR, "%s(): 4th param must be an array/null +value!", get_active_function_name()); + goto clean_exit; + } + } + + + if (argc >= 5) { + if ((*zflags)->type != IS_ARRAY) { + zend_error(E_ERROR, "%s(): 5th param must be an array!", +get_active_function_name()); + goto clean_exit; + } + flags = php_openssl_parse_pkcs7_flags(zflags, 0); + } + + if ((*zrecipcerts)->type != IS_ARRAY) { + zend_error(E_ERROR, "%s(): 3rd parameter must be an array", +get_active_function_name()); + goto clean_exit; + } + + infile = BIO_new_file(Z_STRVAL_PP(zinfilename), "r"); + if (infile == NULL) + goto clean_exit; + + outfile = BIO_new_file(Z_STRVAL_PP(zoutfilename), "w"); + if (outfile == NULL) + goto clean_exit; + + recipcerts = sk_X509_new_null(); + + /* get certs */ + zend_hash_internal_pointer_reset_ex(HASH_OF(*zrecipcerts), &hpos); + while(zend_hash_get_current_data_ex(HASH_OF(*zrecipcerts), (void**)&zcertval, +&hpos) == SUCCESS) { + long certresource; + + cert = php_openssl_x509_from_zval(zcertval, 0, &certresource); + if (cert == NULL) + goto clean_exit; + + if (certresource != -1) { + /* we shouldn't free this particular cert, as it is a resource. + make a copy and push that on the stack instead */ + cert = X509_dup(cert); + if (cert == NULL) + goto clean_exit; + } + sk_X509_push(recipcerts, cert); + + zend_hash_move_forward_ex(HASH_OF(*zrecipcerts), &hpos); + } + + /* TODO: allow user to choose a different cipher */ + cipher = EVP_rc2_40_cbc(); + if (cipher == NULL) + goto clean_exit; + + p7 = PKCS7_encrypt(recipcerts, infile, cipher, flags); + + if (p7 == NULL) + goto clean_exit; + + /* tack on extra headers */ + zend_hash_internal_pointer_reset_ex(HASH_OF(*zheaders), &hpos); + while(zend_hash_get_current_data_ex(HASH_OF(*zheaders), (void**)&zcertval, +&hpos) == SUCCESS) { + zend_hash_get_current_key_ex(HASH_OF(*zheaders), &strindex, +&strindexlen, &intindex, 0, &hpos); + + convert_to_string_ex(zcertval); + + if (strindex) + BIO_printf(outfile, "%s: %s\n", strindex, +Z_STRVAL_PP(zcertval)); + else + BIO_printf(outfile, "%s\n", Z_STRVAL_PP(zcertval)); + + zend_hash_move_forward_ex(HASH_OF(*zheaders), &hpos); + } + + BIO_reset(infile); + + /* write the encrypted data */ + SMIME_write_PKCS7(outfile, p7, infile, flags); + + RETVAL_TRUE; + + +clean_exit: + PKCS7_free(p7); + BIO_free(infile); + BIO_free(outfile); + if (recipcerts) + sk_X509_pop_free(recipcerts, X509_free); + } /* }}} */ -#endif + +/* {{{ proto bool openssl_pkcs7_sign(string infile, string outfile, mixed signcert, +mixed signkey, array headers[, array flags][, string extracertsfilename]) + sign the MIME message in the file named infile with signcert/signkey and +output the result to file name outfile. headers lists plain text headers to exclude +from the signed portion of the message, and should include to, from and subject as a +minimum */ + +PHP_FUNCTION(openssl_pkcs7_sign) +{ + zval ** zinfilename, ** zoutfilename, ** zcert, ** zprivkey, ** zheaders, ** +zflags = NULL, ** zextracerts; + zval ** hval; + X509 * cert = NULL; + EVP_PKEY * privkey = NULL; + int flags = PKCS7_DETACHED, argc; + PKCS7 * p7 = NULL; + BIO * infile = NULL, * outfile = NULL; + STACK_OF(X509) *others = NULL; + long certresource = -1, keyresource = -1; + ulong strindexlen, intindex; + HashPosition hpos; + char * strindex; + + argc = ZEND_NUM_ARGS(); + + RETVAL_FALSE; + + if (argc < 5 || argc > 7 || zend_get_parameters_ex(argc, &zinfilename, +&zoutfilename, &zcert, &zprivkey, &zheaders, &zflags, &zextracerts) == FAILURE) + { + WRONG_PARAM_COUNT; + } -/* {{{ proto bool openssl_sign(string data, string signature, int key) + if (argc >= 7) { + convert_to_string_ex(zextracerts); + others = load_all_certs_from_file(Z_STRVAL_PP(zextracerts)); + if (others == NULL) + goto clean_exit; + } + + if (argc >= 6) { + if ((*zflags)->type != IS_ARRAY) { + zend_error(E_ERROR, "%s(): 6th param must be an array!", +get_active_function_name()); + goto clean_exit; + } + flags = php_openssl_parse_pkcs7_flags(zflags, PKCS7_DETACHED); + } + if (argc >= 5) { + if ((*zheaders)->type == IS_NULL) + zheaders = NULL; + else if ((*zheaders)->type != IS_ARRAY) { + zend_error(E_ERROR, "%s(): 5th param must be an array/null +value!", get_active_function_name()); + goto clean_exit; + } + } + + convert_to_string_ex(zinfilename); + convert_to_string_ex(zoutfilename); + + privkey = php_openssl_evp_from_zval(zprivkey, 0, "", 0, &keyresource); + if (privkey == NULL) + goto clean_exit; + + cert = php_openssl_x509_from_zval(zcert, 0, &certresource); + if (cert == NULL) + goto clean_exit; + + infile = BIO_new_file(Z_STRVAL_PP(zinfilename), "r"); + if (infile == NULL) + goto clean_exit; + + outfile = BIO_new_file(Z_STRVAL_PP(zoutfilename), "w"); + if (outfile == NULL) + goto clean_exit; + + p7 = PKCS7_sign(cert, privkey, others, infile, flags); + if (p7 == NULL) { + zend_error(E_ERROR, "%s(): error creating PKCS7 structure!", +get_active_function_name()); + goto clean_exit; + } + + BIO_reset(infile); + + /* tack on extra headers */ + 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 ** infilename, ** outfilename, ** recipcert, ** recipkey; + int argc = ZEND_NUM_ARGS(); + X509 * cert = NULL; + EVP_PKEY * key = NULL; + long certresval, keyresval; + BIO * in = NULL, * out = NULL, * datain = NULL; + PKCS7 * p7 = NULL; + + if (argc > 4 || argc < 3 || zend_get_parameters_ex(argc, &infilename, +&outfilename, &recipcert, &recipkey) == FAILURE) { + WRONG_PARAM_COUNT; + } + + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(recipcert, 0, &certresval); + if (cert == NULL) { + zend_error(E_ERROR, "%s(): unable to coerce param 3 to x509 cert", +get_active_function_name()); + goto clean_exit; + } + + key = php_openssl_evp_from_zval(argc == 3 ? recipcert : recipkey, 0, "", 0, +&keyresval); + if (key == NULL) { + zend_error(E_ERROR, "%s(): unable to coerce param %d to a private +key", get_active_function_name(), argc); + goto clean_exit; + } + + convert_to_string_ex(outfilename); + convert_to_string_ex(infilename); + + in = BIO_new_file(Z_STRVAL_PP(infilename), "r"); + if (in == NULL) { + goto clean_exit; + } + out = BIO_new_file(Z_STRVAL_PP(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_sign(string data, &string signature, mixed key) Sign data */ PHP_FUNCTION(openssl_sign) { @@ -281,6 +1275,7 @@ EVP_PKEY *pkey; int siglen; unsigned char *sigbuf; + long keyresource = -1; EVP_MD_CTX md_ctx; if (ZEND_NUM_ARGS() != 3 || @@ -288,8 +1283,13 @@ WRONG_PARAM_COUNT; } convert_to_string_ex(data); - - ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, key, -1, "OpenSSL key", le_key); + + pkey = php_openssl_evp_from_zval(key, 0, "", 0, &keyresource); + if (pkey == NULL) { + zend_error(E_ERROR, "%s(): supplied key param cannot be coerced into a +private key", get_active_function_name()); + RETURN_FALSE; + } + siglen = EVP_PKEY_size(pkey); sigbuf = emalloc(siglen + 1); @@ -299,15 +1299,17 @@ zval_dtor(*signature); sigbuf[siglen] = '\0'; ZVAL_STRINGL(*signature, sigbuf, siglen, 0); - RETURN_TRUE; + RETVAL_TRUE; } else { efree(sigbuf); - RETURN_FALSE; + RETVAL_FALSE; } + if (keyresource == -1) + EVP_PKEY_free(pkey); } /* }}} */ -/* {{{ proto int openssl_verify(string data, string signature, int key) +/* {{{ proto int openssl_verify(string data, string signature, mixed key) Verify data */ PHP_FUNCTION(openssl_verify) { @@ -315,6 +1317,7 @@ EVP_PKEY *pkey; int err; EVP_MD_CTX md_ctx; + long keyresource = -1; if (ZEND_NUM_ARGS() != 3 || zend_get_parameters_ex(3, &data, &signature, &key) == FAILURE) { @@ -323,70 +1326,80 @@ convert_to_string_ex(data); convert_to_string_ex(signature); - ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, key, -1, "OpenSSL key", le_key); + pkey = php_openssl_evp_from_zval(key, 1, NULL, 0, &keyresource); + if (pkey == NULL) { + zend_error(E_ERROR, "%s(): supplied key param cannot be coerced into a +public key", get_active_function_name()); + RETURN_FALSE; + } EVP_VerifyInit (&md_ctx, EVP_sha1()); EVP_VerifyUpdate (&md_ctx, Z_STRVAL_PP(data), Z_STRLEN_PP(data)); err = EVP_VerifyFinal (&md_ctx, Z_STRVAL_PP(signature), Z_STRLEN_PP(signature), pkey); + + if (keyresource == -1) + EVP_PKEY_free(pkey); + RETURN_LONG(err); } /* }}} */ -/* {{{ proto int openssl_seal(string data, string sealdata, array ekeys, array pubkeys) +/* {{{ proto int openssl_seal(string data, &string sealdata, &array ekeys, array +pubkeys) Seal data */ PHP_FUNCTION(openssl_seal) { zval **pubkeys, **pubkey, **data, **sealdata, **ekeys; HashTable *pubkeysht; - HashPosition pos; + HashPosition pos; EVP_PKEY **pkeys; + long * key_resources; /* so we know what to cleanup */ int i, len1, len2, *eksl, nkeys; - unsigned char *buf, **eks; + unsigned char *buf = NULL, **eks; EVP_CIPHER_CTX ctx; if (ZEND_NUM_ARGS() != 4 || - zend_get_parameters_ex(4, &data, &sealdata, &ekeys, - &pubkeys) == FAILURE) { + zend_get_parameters_ex(4, &data, &sealdata, &ekeys, + &pubkeys) == FAILURE) { WRONG_PARAM_COUNT; } SEPARATE_ZVAL(pubkeys); - pubkeysht = HASH_OF(*pubkeys); + pubkeysht = HASH_OF(*pubkeys); nkeys = pubkeysht ? zend_hash_num_elements(pubkeysht) : 0; if (!nkeys) { - php_error(E_WARNING, - "Fifth argument to openssl_seal() must be a non-empty array"); - RETURN_FALSE; + php_error(E_WARNING, + "Fourth argument to openssl_seal() must be a non-empty +array"); + RETURN_FALSE; } pkeys = emalloc(nkeys * sizeof(*pkeys)); eksl = emalloc(nkeys * sizeof(*eksl)); eks = emalloc(nkeys * sizeof(*eks)); - + key_resources = emalloc(nkeys * sizeof(long)); + convert_to_string_ex(data); - zend_hash_internal_pointer_reset_ex(pubkeysht, &pos); + /* get the public keys we are using to seal this data */ + zend_hash_internal_pointer_reset_ex(pubkeysht, &pos); i = 0; - while (zend_hash_get_current_data_ex(pubkeysht, (void **) &pubkey, - &pos) == SUCCESS) { - ZEND_FETCH_RESOURCE(pkeys[i], EVP_PKEY *, pubkey, -1, - "OpenSSL key", le_key); + while (zend_hash_get_current_data_ex(pubkeysht, (void **) &pubkey, + &pos) == SUCCESS) { + pkeys[i] = php_openssl_evp_from_zval(pubkey, 1, NULL, 0, +&key_resources[i]); + if (pkeys[i] == NULL) { + zend_error(E_ERROR, "%s(): not a public key (%dth member of +pubkeys)", get_active_function_name(), i); + RETVAL_FALSE; + goto clean_exit; + } eks[i] = emalloc(EVP_PKEY_size(pkeys[i]) + 1); - zend_hash_move_forward_ex(pubkeysht, &pos); + zend_hash_move_forward_ex(pubkeysht, &pos); i++; } #if OPENSSL_VERSION_NUMBER >= 0x0090600fL if (!EVP_EncryptInit(&ctx,EVP_rc4(),NULL,NULL)) { - for (i=0; i<nkeys; i++) { - efree(eks[i]); - } - efree(eks); - efree(eksl); - efree(pkeys); - RETURN_FALSE; + RETVAL_FALSE; + goto clean_exit; } #else EVP_EncryptInit(&ctx,EVP_rc4(),NULL,NULL); @@ -402,18 +1415,14 @@ if (!EVP_SealInit(&ctx, EVP_rc4(), eks, eksl, NULL, pkeys, nkeys) #if OPENSSL_VERSION_NUMBER >= 0x0090600fL - || !EVP_SealUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data), - Z_STRLEN_PP(data)) + || !EVP_SealUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data), + Z_STRLEN_PP(data)) #endif - ) { + ) { + RETVAL_FALSE; efree(buf); - for (i=0; i<nkeys; i++) { - efree(eks[i]); - } - efree(eks); - efree(eksl); - efree(pkeys); - RETURN_FALSE; + goto clean_exit; + } #if OPENSSL_VERSION_NUMBER < 0x0090600fL @@ -421,33 +1430,24 @@ #endif EVP_SealFinal(&ctx, buf + len1, &len2); - efree(pkeys); - if (len1 + len2 > 0) { zval_dtor(*sealdata); buf[len1 + len2] = '\0'; - ZVAL_STRINGL(*sealdata, erealloc(buf, len1 + len2 + 1), - len1 + len2, 0); + buf = erealloc(buf, len1 + len2 + 1); + ZVAL_STRINGL(*sealdata, buf, len1 + len2, 0); zval_dtor(*ekeys); if (array_init(*ekeys) == FAILURE) { php_error(E_ERROR, "Cannot initialize return value"); - for (i=0; i<nkeys; i++) { - efree(eks[i]); - } - efree(eks); - efree(eksl); - RETURN_FALSE; + RETVAL_FALSE; + efree(buf); + goto clean_exit; } for (i=0; i<nkeys; i++) { eks[i][eksl[i]] = '\0'; - add_next_index_stringl(*ekeys, - erealloc(eks[i], eksl[i] + 1), - eksl[i], 0); + add_next_index_stringl(*ekeys, erealloc(eks[i], eksl[i] + 1), +eksl[i], 0); + eks[i] = NULL; } - efree(eks); - efree(eksl); - #if 0 /* If allow ciphers that need IV, we need this */ zval_dtor(*ivec); @@ -458,64 +1458,80 @@ ZVAL_EMPTY_STRING(*ivec); } #endif - } else { + } + else efree(buf); - for (i=0; i<nkeys; i++) { + + RETVAL_LONG(len1 + len2); + +clean_exit: + for (i=0; i<nkeys; i++) { + if (key_resources[i] == -1) + EVP_PKEY_free(pkeys[i]); + if (eks[i]) efree(eks[i]); - } - efree(eks); - efree(eksl); } - - RETURN_LONG(len1 + len2); + efree(eks); + efree(eksl); + efree(pkeys); + efree(key_resources); } /* }}} */ -/* {{{ proto bool openssl_open(string data, string opendata, string ekey, int privkey) +/* {{{ proto bool openssl_open(string data, &string opendata, string ekey, mixed +privkey) Open data */ PHP_FUNCTION(openssl_open) { zval **privkey, **data, **opendata, **ekey; EVP_PKEY *pkey; - int len1, len2, ekl; - unsigned char *buf, *ek; - + int len1, len2; + unsigned char *buf; + long keyresource = -1; EVP_CIPHER_CTX ctx; if (ZEND_NUM_ARGS() != 4 || - zend_get_parameters_ex(4, &data, &opendata, &ekey, - &privkey) == FAILURE) { + zend_get_parameters_ex(4, &data, &opendata, &ekey, + &privkey) == FAILURE) { WRONG_PARAM_COUNT; } convert_to_string_ex(data); convert_to_string_ex(ekey); - - ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, privkey, -1, "OpenSSL key", - le_key); + pkey = php_openssl_evp_from_zval(privkey, 0, "", 0, &keyresource); + if (pkey == NULL) { + zend_error(E_ERROR, "%s(): unable to coerce param 4 into a private +key", get_active_function_name()); + RETURN_FALSE; + } buf = emalloc(Z_STRLEN_PP(data) + 1); if (EVP_OpenInit(&ctx, EVP_rc4(), Z_STRVAL_PP(ekey), - Z_STRLEN_PP(ekey), NULL, pkey) + Z_STRLEN_PP(ekey), NULL, pkey) #if OPENSSL_VERSION_NUMBER >= 0x0090600fL - && EVP_OpenUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data), - Z_STRLEN_PP(data)) + && EVP_OpenUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data), + Z_STRLEN_PP(data)) #endif - ) { + ) { #if OPENSSL_VERSION_NUMBER < 0x0090600fL EVP_OpenUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data), - Z_STRLEN_PP(data)); + Z_STRLEN_PP(data)); #endif if (!EVP_OpenFinal(&ctx, buf + len1, &len2) || - (len1 + len2 == 0)) { + (len1 + len2 == 0)) { efree(buf); + if (keyresource == -1) + EVP_PKEY_free(pkey); RETURN_FALSE; } } else { efree(buf); + if (keyresource == -1) + EVP_PKEY_free(pkey); + RETURN_FALSE; } - + if (keyresource == -1) + EVP_PKEY_free(pkey); + zval_dtor(*opendata); buf[len1 + len2] = '\0'; ZVAL_STRINGL(*opendata, erealloc(buf, len1 + len2 + 1), len1 + len2, 0); @@ -531,7 +1547,6 @@ } /* }}} */ -#if 0 /* {{{ _php_x509_free() */ static void _php_x509_free(zend_rsrc_list_entry *rsrc) { @@ -539,7 +1554,6 @@ X509_free(x509); } /* }}} */ -#endif /* * Local variables: Index: php4/ext/openssl/php_openssl.h diff -u php4/ext/openssl/php_openssl.h:1.5 php4/ext/openssl/php_openssl.h:1.6 --- php4/ext/openssl/php_openssl.h:1.5 Sun Feb 25 22:07:10 2001 +++ php4/ext/openssl/php_openssl.h Sun Apr 1 16:06:14 2001 @@ -16,7 +16,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: php_openssl.h,v 1.5 2001/02/26 06:07:10 andi Exp $ */ +/* $Id: php_openssl.h,v 1.6 2001/04/01 23:06:14 wez Exp $ */ #ifndef PHP_OPENSSL_H #define PHP_OPENSSL_H @@ -31,15 +31,21 @@ PHP_FUNCTION(openssl_get_privatekey); PHP_FUNCTION(openssl_get_publickey); PHP_FUNCTION(openssl_free_key); -#if 0 -PHP_FUNCTION(openssl_read_publickey); -PHP_FUNCTION(openssl_read_x509); -PHP_FUNCTION(openssl_free_x509); -#endif +PHP_FUNCTION(openssl_x509_read); +PHP_FUNCTION(openssl_x509_free); PHP_FUNCTION(openssl_sign); PHP_FUNCTION(openssl_verify); PHP_FUNCTION(openssl_seal); PHP_FUNCTION(openssl_open); + +PHP_FUNCTION(openssl_pkcs7_verify); +PHP_FUNCTION(openssl_pkcs7_decrypt); +PHP_FUNCTION(openssl_pkcs7_sign); +PHP_FUNCTION(openssl_pkcs7_encrypt); + +PHP_FUNCTION(openssl_error_string); +PHP_FUNCTION(openssl_x509_parse); +PHP_FUNCTION(openssl_x509_checkpurpose); #else
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED] To contact the list administrators, e-mail: [EMAIL PROTECTED]