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?
--- 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)
/* Filename: init_oo.cc Object oriented OpenSSL interface for PHP5 200600505 Created by Brandon Fosdick <[EMAIL PROTECTED]> */ #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<typename T> zend_class_entry* php::ObjectBase<T>::class_entry = NULL; template<typename T> zend_object_handlers php::ObjectBase<T>::handlers; template<typename T> zend_internal_function php::ObjectBase<T>::constructor_function; namespace php { namespace openssl { extern "C" void init_objects(TSRMLS_D) { PrivateKey::register_class(TSRMLS_C); PublicKey::register_class(TSRMLS_C); } } }
/* Filename: objbase.h Base class for wrapping PHP5 Object Oriented interface 20060501 Created by Brandon Fosdick <[EMAIL PROTECTED]> */ extern "C" { #include "php.h" } #ifndef OBJBASE_H #define OBJBASE_H namespace php { template<typename T> 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<zend_object*>(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<obj_t*>(static_cast<zend_object*>(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<zend_function*>(&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<typename T> inline T* get_object(zval* z TSRMLS_DC) { return static_cast<T*>(static_cast<zend_object*>(zend_object_store_get_object(z TSRMLS_CC))); } inline zend_object* get_object(zval* z TSRMLS_DC) { return static_cast<zend_object*>(zend_object_store_get_object(z TSRMLS_CC)); } } //namespace php #endif //OBJBASE_H
--- openssl.c.orig Sun Apr 30 16:43:40 2006 +++ openssl.c Fri May 12 15:53:46 2006 @@ -41,7 +41,8 @@ #include <openssl/err.h> #include <openssl/conf.h> #include <openssl/rand.h> -#include <openssl/ssl.h> + +#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;
/* Filename: openssl.h Most of this was moved from openssl.c 20060510 Created by Brandon Fosdick <[EMAIL PROTECTED]> */ #include <openssl/conf.h> #include <openssl/ssl.h> #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
/* Filename: privkey.cc Private key interface class for PHP5 20060429 Created by Brandon Fosdick <[EMAIL PROTECTED]> */ #include "privkey.h" #include "openssl.h" namespace php { namespace openssl { template<> const char *const ObjectBase<PrivateKey>::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<PrivateKey>(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<PrivateKey>(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<PrivateKey>(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<PrivateKey>::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
/* Filename: privkey.h Private key interface class for PHP5 20060501 Created by Brandon Fosdick <[EMAIL PROTECTED]> */ #include <string> #include <openssl/ssl.h> #include "objbase.h" #include "pubkey.h" #ifndef PRIVKEY_H #define PRIVKEY_H namespace php { namespace openssl { class PrivateKey : public ObjectBase<PrivateKey> { EVP_PKEY* key; unsigned numBits; PrivateKey(const PrivateKey&); //No copying public: //Bare-minimum constructor PrivateKey(zend_class_entry *zce) : key(NULL), numBits(0), ObjectBase<PrivateKey>(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
/* Filename: pubkey.cc Public key interface class for PHP5 20060429 Created by Brandon Fosdick <[EMAIL PROTECTED]> */ #include "pubkey.h" #define RSA_PUBLIC_HEADER "-----BEGIN RSA PUBLIC KEY-----" namespace php { namespace openssl { template<> const char *const ObjectBase<PublicKey>::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<PublicKey>(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<PublicKey>(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<PublicKey>::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
/* Filename: pubkey.h Public key interface class for PHP5 20060501 Created by Brandon Fosdick <[EMAIL PROTECTED]> */ #include <openssl/ssl.h> #include "objbase.h" #ifndef PUBKEY_H #define PUBKEY_H namespace php { namespace openssl { class PublicKey : public ObjectBase<PublicKey> { EVP_PKEY* key; PublicKey(const PublicKey&); //No copying public: //Bare-minimum constructor PublicKey(zend_class_entry *zce) : key(NULL), ObjectBase<PublicKey>(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<PublicKey>::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
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php