Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:23325 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 61067 invoked by uid 1010); 12 May 2006 23:39:30 -0000 Delivered-To: ezmlm-scan-internals@lists.php.net Delivered-To: ezmlm-internals@lists.php.net Received: (qmail 61052 invoked from network); 12 May 2006 23:39:30 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 12 May 2006 23:39:30 -0000 X-PHP-List-Original-Sender: bfoz@bfoz.net X-Host-Fingerprint: 204.127.200.84 sccrmhc14.comcast.net NetCache Data OnTap 5.x Received: from ([204.127.200.84:47808] helo=sccrmhc14.comcast.net) by pb1.pair.com (ecelerity 2.0 beta r(6323M)) with SMTP id 84/A8-19568-2BC15644 for ; Fri, 12 May 2006 19:39:30 -0400 Received: from [192.168.0.5] (c-24-6-134-233.hsd1.ca.comcast.net[24.6.134.233]) by comcast.net (sccrmhc14) with ESMTP id <20060512233925014005h419e>; Fri, 12 May 2006 23:39:26 +0000 Message-ID: <44651CAC.4080007@bfoz.net> Date: Fri, 12 May 2006 16:39:24 -0700 User-Agent: Thunderbird 1.5.0.2 (X11/20060507) MIME-Version: 1.0 To: internals@lists.php.net, kingwez@gmail.com X-Enigmail-Version: 0.94.0.0 Content-Type: multipart/mixed; boundary="------------090900000204090506020907" Subject: [PATCH] Preliminary OpenSSL Changes (PEM) From: bfoz@bfoz.net (Brandon Fosdick) --------------090900000204090506020907 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit I'm not finished yet, but I'm at a decent check point, so I thought I'd send out what I've done so far with the hope of getting some feedback. The attached files contain the patches and new files (wrt 5.1.4) for implementing import and export of PEM strings for both public and private keys using an OO interface. BTW, I know the code doesn't conform to the CS very well, I plan to run everything through astyle at the very end. One of my initial goals was to avoid modifying existing code as much as possible to avoid introducing new breakage. In hindsight that might have been more trouble than it was worth because I ended up doing a lot more than I had originally planned. But it was a good learning experience. I decided on an OO interface because it seemed like it could be most easily accomplished w/o touching existing code. At present the new interface adds two classes, described below. I wasn't sure what to name them, so feel free to offer suggestions. class PrivateKey constructor( [string pem [, string passphrase]] ) constructor( [array configargs]] ) string pem( [ string passphrase [, encrypt_key]] ) PublicKey public() class PublicKey constructor(string pem) string pem() I'm not sure about the second constructor for PrivateKey, since it really only needs two of the configarg values. I'm thinking about having it take just those two as separate arguments. Most of the changes are in the new files. In openssl.c I added a call to init_object() in the MINIT function and moved struct php_x509_request to openssl.h so I could use it elsewhere. I also made a few functions non-static so they could be called from the other files. From here I think I might as well add an object for certificates (X509, X509Certificate, Certificate...?) and then I'll add PKCS12 support. Thoughts, suggestions? Problems? --------------090900000204090506020907 Content-Type: text/plain; name="config.m4.diff" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="config.m4.diff" --- config0.m4.orig Sat Jan 1 06:32:58 2005 +++ config.m4 Wed May 10 15:14:22 2006 @@ -9,8 +9,10 @@ [ --with-kerberos[=DIR] OPENSSL: Include Kerberos support], no, no) if test "$PHP_OPENSSL" != "no"; then - PHP_NEW_EXTENSION(openssl, openssl.c xp_ssl.c, $ext_shared) + PHP_REQUIRE_CXX() + PHP_NEW_EXTENSION(openssl, openssl.c xp_ssl.c init_oo.cc pubkey.cc privkey.cc, $ext_shared) PHP_SUBST(OPENSSL_SHARED_LIBADD) + PHP_ADD_LIBRARY(stdc++, 1, OPENSSL_SHARED_LIBADD) if test "$PHP_KERBEROS" != "no"; then PHP_SETUP_KERBEROS(OPENSSL_SHARED_LIBADD) --------------090900000204090506020907 Content-Type: text/plain; name="init_oo.cc" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="init_oo.cc" /* Filename: init_oo.cc Object oriented OpenSSL interface for PHP5 200600505 Created by Brandon Fosdick */ #include "privkey.h" #include "pubkey.h" //Make storage for static members of ObjectBase // These are the members that don't need type-specific defaults template zend_class_entry* php::ObjectBase::class_entry = NULL; template zend_object_handlers php::ObjectBase::handlers; template zend_internal_function php::ObjectBase::constructor_function; namespace php { namespace openssl { extern "C" void init_objects(TSRMLS_D) { PrivateKey::register_class(TSRMLS_C); PublicKey::register_class(TSRMLS_C); } } } --------------090900000204090506020907 Content-Type: text/plain; name="objbase.h" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="objbase.h" /* Filename: objbase.h Base class for wrapping PHP5 Object Oriented interface 20060501 Created by Brandon Fosdick */ extern "C" { #include "php.h" } #ifndef OBJBASE_H #define OBJBASE_H namespace php { template class ObjectBase : public zend_object { typedef T obj_t; protected: static const char *const name; static zend_class_entry* class_entry; //A Class Entry for every derived type static zend_object_handlers handlers; //Handlers for every derived type static zend_internal_function constructor_function; //Function entry for the new-handler static zend_function_entry methods[]; //Handlers for derived type userland-methods public: //Base constructor ObjectBase(zend_class_entry *zce) { ce = zce; ALLOC_HASHTABLE(properties); zend_hash_init(properties, 0, NULL, ZVAL_PTR_DTOR, 0); guards = NULL; //Init unused pointer zval *tmp; zend_hash_copy(properties, &zce->default_properties, (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval*)); } ~ObjectBase() { //Explicitly destroy the zend_object base (it doesn't have its own destructor) zend_object_std_dtor(static_cast(this) TSRMLS_CC); } void* operator new(size_t s) { return emalloc(s); } void operator delete(void* p, size_t) { efree(p); } //Register the class static void register_class(TSRMLS_D) { zend_class_entry ce; memset(&ce, NULL, sizeof(ce)); INIT_CLASS_ENTRY(ce, name, methods); ce.name_length = strlen(name); //Work-a-round for "feature" in INIT_CLASS_ENTRY ce.create_object = &construct; //psuedo-constructor class_entry = zend_register_internal_class(&ce TSRMLS_CC); if( class_entry != NULL ) class_entry->ce_flags |= ZEND_ACC_FINAL_CLASS; handlers = *zend_get_std_object_handlers(); handlers.clone_obj = NULL; //No cloning handlers.get_constructor = &get_constructor; } //Construct and register a new unitialized object in a zval // Used by handlers that need to return a new object // Uses the static zend_class_entry object, so register_class() must be called first // *** This is a bare-minimum psuedo-contructor that calls the simplest constructor // possible, and may leave the class mostly uninitialized. The class probably // still needs to be initialized in the new()-handler. static obj_t* construct(zval *z TSRMLS_DC) { if(class_entry == NULL) //Ignore calls prior to register_class() return NULL; obj_t* obj = new obj_t(class_entry); //New object with registered zend_class_entry z->type = IS_OBJECT; z->value.obj = obj->store(TSRMLS_C); return obj; } private: //Construct and then put on the object store // This is function passed to the ZE by get_constructor() // *** This is a bare-minimum psuedo-contructor that calls the simplest constructor // possible, and may leave the class mostly uninitialized. The class probably // still needs to be initialized in the new()-handler. static zend_object_value construct(zend_class_entry *zce TSRMLS_DC) { return (new obj_t(zce))->store(TSRMLS_C); } //A destructor for every derived type static void destroy(void* object TSRMLS_DC) { delete static_cast(static_cast(object)); } //Make a zend_function for the derived class' constructor handler static zend_function* get_constructor(zval* object TSRMLS_DC) { zend_object* obj = zend_objects_get_address(object TSRMLS_CC); if(obj == NULL) return NULL; memset(&constructor_function, NULL, sizeof(constructor_function)); constructor_function.type = ZEND_INTERNAL_FUNCTION; constructor_function.function_name = obj->ce->name; constructor_function.scope = obj->ce; constructor_function.handler = &obj_t::ZEND_FN(construct); return reinterpret_cast(&constructor_function); } //Register the object with the ZE object store // This function is private to help prevent being called more than once per object zend_object_value store(TSRMLS_D) { zend_object_value r; //Downcast to a zend_object* to appease the standard object handlers r.handle = zend_objects_store_put((zend_object*)this, NULL, &destroy, NULL TSRMLS_CC); r.handlers = &handlers; return r; } }; template inline T* get_object(zval* z TSRMLS_DC) { return static_cast(static_cast(zend_object_store_get_object(z TSRMLS_CC))); } inline zend_object* get_object(zval* z TSRMLS_DC) { return static_cast(zend_object_store_get_object(z TSRMLS_CC)); } } //namespace php #endif //OBJBASE_H --------------090900000204090506020907 Content-Type: text/plain; name="openssl.c.diff" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="openssl.c.diff" --- openssl.c.orig Sun Apr 30 16:43:40 2006 +++ openssl.c Fri May 12 15:53:46 2006 @@ -41,7 +41,8 @@ #include #include #include -#include + +#include "openssl.h" #define DEFAULT_KEY_LENGTH 512 #define MIN_KEY_LENGTH 384 @@ -202,32 +203,13 @@ /* true global; readonly after module startup */ static char default_ssl_conf_filename[MAXPATHLEN]; -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 int php_openssl_is_private_key(EVP_PKEY* pkey 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); +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 TSRMLS_DC) @@ -427,7 +409,7 @@ -static int php_openssl_parse_config( +int php_openssl_parse_config( struct php_x509_request * req, zval * optional_args TSRMLS_DC @@ -589,6 +571,8 @@ 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); + init_objects(TSRMLS_C); /* Initialize the OO interface */ + SSL_library_init(); OpenSSL_add_all_ciphers(); OpenSSL_add_all_digests(); @@ -1873,7 +1857,7 @@ /* }}} */ /* {{{ php_openssl_generate_private_key */ -static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req TSRMLS_DC) +EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req TSRMLS_DC) { char * randfile = NULL; int egdsocket, seeded; --------------090900000204090506020907 Content-Type: text/plain; name="openssl.h" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="openssl.h" /* Filename: openssl.h Most of this was moved from openssl.c 20060510 Created by Brandon Fosdick */ #include #include #ifndef OPENSSL_H #define OPENSSL_H 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; }; #ifdef __cplusplus extern "C" { #endif int php_openssl_parse_config(struct php_x509_request*, zval* TSRMLS_DC); EVP_PKEY* php_openssl_generate_private_key(struct php_x509_request* TSRMLS_DC); #ifdef __cplusplus } namespace php { namespace openssl { class X509Request : public php_x509_request { public: X509Request() { global_config = NULL; req_config = NULL; md_alg = NULL; digest = NULL;; section_name = NULL; config_filename = NULL; digest_name = NULL; extensions_section = NULL; request_extensions_section = NULL; priv_key_bits = 0; priv_key_type = 0; priv_key_encrypt = 0; EVP_PKEY * priv_key = NULL; } //This doesn't call php_openssl_dispose_config() because there's no way to pass // TSRMLS_* to a destructor // *** This assumes that the EVP_PKEY is managed elsewhere ~X509Request() { if( global_config != NULL ) { CONF_free(global_config); global_config = NULL; } if( req_config != NULL ) { CONF_free(req_config); req_config = NULL; } } bool parse_config(zval* z) { return php_openssl_parse_config(&base(), z TSRMLS_CC) == SUCCESS; } EVP_PKEY* generate_private_key(TSRMLS_D) { return php_openssl_generate_private_key(&base() TSRMLS_CC); } php_x509_request& base() { return *this; } }; } } #endif //__cplusplus #endif //OPENSSL_H --------------090900000204090506020907 Content-Type: text/plain; name="privkey.cc" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="privkey.cc" /* Filename: privkey.cc Private key interface class for PHP5 20060429 Created by Brandon Fosdick */ #include "privkey.h" #include "openssl.h" namespace php { namespace openssl { template<> const char *const ObjectBase::name = "PrivateKey"; //Set the class's userland name //Do the actual construction in response to a userland call to 'new PrivateKey()' // The first argument can be a config_args array // It could also be a PEM string, in which case the 2nd arg is an optional passphrase // The passphrase could be a zero-length string, which isn't considered to be a passphrase PHP_NAMED_FUNCTION(PrivateKey::ZEND_FN(construct)) { zval *z = NULL; unsigned char* pass = NULL; int pass_len = 0; if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z!s", &z, &pass, &pass_len) == FAILURE ) { RETURN_FALSE; } //Get the mostly-unitialized object off the object store PrivateKey* key = get_object(getThis() TSRMLS_CC); if( key == NULL ) { RETURN_FALSE; } //Now behave like a constructor if( (z != NULL) && (Z_TYPE_P(z) == IS_STRING) ) //Create the key from a PEM string { BIO *in = BIO_new_mem_buf(Z_STRVAL_P(z), Z_STRLEN_P(z)); if(in == NULL) { RETURN_FALSE; } pass = (pass_len==0)?NULL:pass; //Ignore zero-length passphrases key->key = PEM_read_bio_PrivateKey(in, NULL, NULL, pass); BIO_free(in); } else //Generate a new key { X509Request req; req.parse_config(z); key->numBits = req.priv_key_bits; //Save the number of key bits key->key = req.generate_private_key(TSRMLS_C); } } namespace privatekey { //Return the private key as PEM formatted text PHP_FUNCTION(pem) { unsigned char* pass = NULL; int pass_len = 0; zend_bool encrypt = false; RETVAL_FALSE; //Default retval to false (speedup) if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!b", &pass, &pass_len, &encrypt) == FAILURE) { return; } PrivateKey* key = get_object(getThis() TSRMLS_CC); if( key == NULL ) { return; } key->pem(return_value, pass, pass_len, encrypt); } //Return a PublicKey object for this private key PHP_FUNCTION(public) { PrivateKey* key = get_object(getThis() TSRMLS_CC); if( key == NULL ) { RETURN_FALSE; } key->get_public(return_value); // RETURN_STRING("this is public()\n", 1); } } //namespace privatekey #define PRIVKEY_ME(func_name, handler_name) ZEND_FENTRY(func_name, &openssl::privatekey:: PHP_FN(handler_name), NULL, 0) template<> zend_function_entry ObjectBase::methods[] = { // PRIVKEY_ME(csr, csr) //Return a CSR object PRIVKEY_ME(pem, pem) //Return the private key as PEM formatted text PRIVKEY_ME(public, public) //Return a PublicKey object for this private key {NULL, NULL, NULL} }; } //namespace openssl } //namespace php --------------090900000204090506020907 Content-Type: text/plain; name="privkey.h" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="privkey.h" /* Filename: privkey.h Private key interface class for PHP5 20060501 Created by Brandon Fosdick */ #include #include #include "objbase.h" #include "pubkey.h" #ifndef PRIVKEY_H #define PRIVKEY_H namespace php { namespace openssl { class PrivateKey : public ObjectBase { EVP_PKEY* key; unsigned numBits; PrivateKey(const PrivateKey&); //No copying public: //Bare-minimum constructor PrivateKey(zend_class_entry *zce) : key(NULL), numBits(0), ObjectBase(zce) {} ~PrivateKey() { if( key != NULL ) { EVP_PKEY_free(key); } } static PHP_FUNCTION(construct); //Handle userland new() bool pem(zval *const out, unsigned char *const pass, size_t len, const bool encrypt) { const EVP_CIPHER* cipher = (pass && encrypt && (len!=0)) ? EVP_des_ede3_cbc() : NULL; BIO* bio_out = BIO_new(BIO_s_mem()); if( PEM_write_bio_PrivateKey(bio_out, key, cipher, pass, len, NULL, NULL) ) { char* bio_mem_ptr; long bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr); ZVAL_STRINGL(out, bio_mem_ptr, bio_mem_len, 1); } if( bio_out ) { BIO_free(bio_out); } return true; } void get_public(zval* z) { PublicKey* pub = PublicKey::construct(z, key); if( pub == NULL ) return; ++key->references; //Inc the ref count to prevent early deletion } }; } //namespace openssl } //namespace php #endif //PRIVKEY_H --------------090900000204090506020907 Content-Type: text/plain; name="pubkey.cc" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="pubkey.cc" /* Filename: pubkey.cc Public key interface class for PHP5 20060429 Created by Brandon Fosdick */ #include "pubkey.h" #define RSA_PUBLIC_HEADER "-----BEGIN RSA PUBLIC KEY-----" namespace php { namespace openssl { template<> const char *const ObjectBase::name = "PublicKey"; //Set the class's userland name PHP_NAMED_FUNCTION(PublicKey::ZEND_FN(construct)) { zval *z; unsigned char* pem = NULL; int pem_len = 0; if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &pem, &pem_len) == FAILURE ) { RETURN_FALSE; } //Get the mostly-unitialized object off the object store PublicKey* key = get_object(getThis() TSRMLS_CC); if( key == NULL ) { RETURN_FALSE; } //Now behave like a constructor if( (pem != NULL) && (pem_len != 0) ) //Create the key from a PEM string { BIO *in = BIO_new_mem_buf(pem, pem_len); //Init the input buffer if( in == NULL) { RETURN_FALSE; } key->key = PEM_read_bio_PUBKEY(in, NULL, NULL, NULL); BIO_free(in); } } namespace publickey { //Return the public key as PEM formatted text PHP_FUNCTION(pem) { PublicKey* key = get_object(getThis() TSRMLS_CC); if( key == NULL ) { RETURN_FALSE; } key->pem(return_value); } } //namespace publickey #define PUBKEY_ME(func_name, handler_name) ZEND_FENTRY(func_name, &openssl::publickey:: ZEND_FN(handler_name), NULL, 0) template<> zend_function_entry ObjectBase::methods[] = { PUBKEY_ME(pem, pem) //Return the public key as PEM formatted text // PUBKEY_ME(spawn, spawn, NULL) {NULL, NULL, NULL} }; } //namespace openssl } //namespace php --------------090900000204090506020907 Content-Type: text/plain; name="pubkey.h" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="pubkey.h" /* Filename: pubkey.h Public key interface class for PHP5 20060501 Created by Brandon Fosdick */ #include #include "objbase.h" #ifndef PUBKEY_H #define PUBKEY_H namespace php { namespace openssl { class PublicKey : public ObjectBase { EVP_PKEY* key; PublicKey(const PublicKey&); //No copying public: //Bare-minimum constructor PublicKey(zend_class_entry *zce) : key(NULL), ObjectBase(zce) {} ~PublicKey() { if( key != NULL ) { EVP_PKEY_free(key); } } bool init(EVP_PKEY* k) { if(key == NULL) { key = k; return true; } return false; } static PHP_FUNCTION(construct); //Handle userland new() //Construct and register a new unitialized object in a zval given an existing key // Used by handlers that need to return a new object // Uses the static zend_class_entry object, so register_class() must be called first static PublicKey* construct(zval *z, EVP_PKEY* new_key TSRMLS_DC) { PublicKey* obj = ObjectBase::construct(z); if( obj == NULL ) return NULL; obj->init(new_key); return obj; } bool pem(zval *const out) { if( (key->type != EVP_PKEY_RSA) && (key->type != EVP_PKEY_RSA2) ) return false; BIO* bio_out = BIO_new(BIO_s_mem()); if( PEM_write_bio_RSA_PUBKEY(bio_out, key->pkey.rsa) ) { char* bio_mem_ptr; long bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr); ZVAL_STRINGL(out, bio_mem_ptr, bio_mem_len, 1); } if( bio_out ) { BIO_free(bio_out); } return true; } }; } //namespace openssl } //namespace php #endif //PUBKEY_H --------------090900000204090506020907--