In 1991, PGP, "Pretty Good" Privacy, was created. Once the OpenPGP specification was released as RFC 2440 [http://tools.ietf.org/html/rfc2440], other vendors started to provide their own implementations, and the GNU world had its own unleashed to the public with a more serious name -- GnuPG: The GNU Privacy Guard (or, even shorter, gpg).
The most well-known userland usage of OpenPGP is email signing and encryption (check out Enigmail for Mozilla Thunderbird). By digitally signing your email messages, recipients with OpenPGP-enabled mail user agents are able to authenticate the email's sender and message. If the message body was modified in any way during transport, the signature will not verify. This capacity for data authentication is why many files available for download on the Internet also provide an OpenPGP digital signature in a .sig file. OpenPGP is much safer than MD5.
Email messages can also be encrypted to specific recipients, even the sender herself, adding privacy to the equation. For the moment, though, let's just focus on the "data authentication" part.
One night, approximately one year ago, I suddenly woke at 3:00 AM with two crazy sets of words in my head: "BEGIN PGP" (part of OpenPGP content and signature delimiters of signed/encrypted messages) and "GET /". Signing HTTP requests with OpenPGP! I realized that could provide Web servers and applications the ability to determine sender and data authenticity. In other words, to know not only who sent a request, but also if it was tampered with during transport (for example, by a malicious transparent proxy sitting between the browser and Web server).
Of course, I couldn't sleep, and my wife was not in the mood to understand my ramblings, so I got up, wrote the idea down, and went back to sleep.
About ten months later, I decided to create a Firefox extension to implement my idea. The first publicly-released version only worked with the HTTP POST method and was ugly, but functional. Basically, I replaced the POST body with a signed version. This turned out to be a bad idea because Web servers and Web applications that didn't understand it would be broken.
I decided I needed feedback. I contacted the IETF OpenPGP Working Group, and via the mailing list, they said: "OpenPGP signing is implemented for newsgroup posts as a set of extra headers. Why don't you use that?" So, I did; I rewrote Enigform. There was no more HTTP POST body tampering, just a set of HTTP headers added to each request: The signature itself, a GnuPG version string, and a Digest Algorithm string.
That did it: I was able to attach a digital signature to every kind of HTTP request (GET, POST, PUT, whatever) and even to AJAX-originated requests. The latter was very nice for AJAX, since one of its main mis-implementations lies in the authentication of those asynchronous requests. All this came without breaking HTTP at all.
Now, let's see how a standard HTTP request looks, for example, for a FORM submission:
POST /pba/test.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:18.104.22.168) Gecko/20070226 Firefox/22.214.171.124 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost/pba/ Content-Type: application/x-www-form-urlencoded Content-Length: 14 variable=value
Here's the same request signed with OpenPGP:
POST /pba/test.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:126.96.36.199) Gecko/20070226 Firefox/188.8.131.52 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost/pba/ X-OpenPGP-Type: S X-OpenPGP-Sig-Fields: body X-OpenPGP-Sig: iD8DBQFF/cRKAlpOsGhXcE0RAjUbAJ0fTtRKlXPe6OMR4kS0pJ7rioOaPwCfQQ0Bmrai0UVXTkDV9KvNw0eDG1Y==EUzv X-OpenPGP-Digest-Algo: SHA1 X-OpenPGP-Version: GnuPG v1.4.6 (GNU/Linux) X-OpenPGP-Agent: Enigform 0.7.6 for Mozilla Firefox Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 14 variable=value
The request is still standard. A Web server or application that does not know what the extra headers mean (and, more importantly, how to use them) will do nothing but ignore them.
Let's do some further analysis on those headers:
To use Enigform, you just need Firefox 1.5+ and GnuPG. For the moment, I'm testing with GNU/Linux, MS Windows, and MacOS X platforms, so if you have another OS (like Solaris), please send me an email, and we will get Enigform working for you and the rest of the community.
Enigform tries to detect where the gpg (or gpg.exe) binary resides. Although I plan to enhance this area, for the moment it works like this:
Under GNU/Linux, it searches for /usr/bin/gpg. Under MS Windows, it tries to get the installation directory for GnuPG from the registry. Under OS X, it searches for /usr/local/bin/gpg (where macgpg gets installed). If GPG is not found, it warns you and asks you to set the full path to it in the Enigform Preferences window. If your OS is not (yet) supported, it just tells you so. Please contact me if you get that message, so more users can try and benefit from Enigform.
When Enigform detects a request whose URI ends in
#Enigform_Sign# (named anchors are safe to use in this
case, although I plan to enhance this by reading a special HTTP header
in a response), or if the "Always Sign" option is enabled, it will try
to sign the body. If the passphrase is unknown, it asks you to input it.
(Enigform can remember it if told to do so, and you can also clear it
from memory using a context menu item).
Enigform calls GnuPG and feeds the passphrase and request body using random temporary filenames that are stored in a random new temporary directory under your OS's temp directory. It also reads the signed version from a file. It deletes the files and the directory that held them as soon as possible, starting with the passphrase file.
Once the signed version is read, the required elements are extracted and converted to the form we saw in the signed request example. The headers are added, and the normal flow of the request is restored.
And that's it! The receiving Web application/server can act, or not, accordingly.
By means of an Apache module I'm developing, mod_auth_openpgp, adding Enigform support to Apache is very simple. Although the module is still in development, it can be used to test this new security schema.
I hope you like the idea, and I wait for your comments!