While PHP is not my favorite language and platform of choice, I have to admit its ease of deployment, and that's one of the reasons I've used it to build some of my web-related projects, including the REST API and the PNG output of HackSense, and even the homepage of my company. Some of these also used CakePHP, which tries to provide the flexibility and “frameworkyness” of Ruby on Rails while keeping it easy to deploy. It also has the capability of simple and rapid REST API development, which I often prefer to the bloatedness of SOAP.
One of the standardized non-functional services of SOAP is WS-Security, and while it's great for authentication and end-to-end signed messages, its encryption scheme not only has a big overhead, but it had been cracked in 2011, thus cannot be considered secure. That being said, I wanted a solution that can be applied to a REST API, does not waste resources (e.g. spawning OS processes per HTTP call), and uses as many existing code as feasible.
The solution I came up with is a new layout for CakePHP that uses the GnuPG module of PHP, which in turn uses the native GnuPG library. This also means, that the keyring of the user running the web server has to be used. Also, Debian (and thus Ubuntu) doesn't ship this module as a package, so it needs to be compiled, but it's no big deal. Here's what I did:
# apt-get install libgpgme11-dev php5-dev # wget http://pecl.php.net/get/gnupg-1.3.2.tgz # tar -xvzf gnupg-1.3.2.tgz # phpize && ./configure && make && make install # echo "extension=gnupg.so" >/etc/php5/conf.d/gnupg.ini # /etc/init.d/apache2 reload
These versions made sense in February 2012, so make sure that libgpgme, PHP
and the PHP GnuPG module refers to the latest version available. After the last
command has executed successfully, PHP scripts should be able to make use of
the GnuPG package. I crafted the following layout in
<?php $gpg = new gnupg(); $gpg->addencryptkey(Configure::read('Gpg.enckey')); $gpg->addsignkey(Configure::read('Gpg.sigkey')); $gpg->setarmor(0); $out = $gpg->encryptsign($content_for_layout); header('Content-Length: ' . strlen($out)); header('Content-Type: application/octet-stream'); print $out; ?>
Configure::read($key), the keys used for making signatures and
encryption can be stored away from the code, I put the following two lines
Configure::write('Gpg.enckey', 'ID of the recipient's public key'); Configure::write('Gpg.sigkey', 'Fingerprint of the signing key');
And at last, actions that require this security layer only need a single line
in the controller code (e.g.
$this->layout = 'gpg';
Make sure to set this as close to the beginning of the function as you can to avoid leaking error messages to attackers triggering errors in the code before the layout is set to the secured one.
And that's it, the layout makes sure that all information sent from the view
is protected both from interception and modification. During testing, I favored
using armored output, I only disabled it after moving it to production, so if
it's needed, only two lines need modification:
setarmor(0) should be
setarmor(1) and the
Content-Type should be set to
text/plain. Have fun!