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