Identity and Data Authentication for HTTP Using OpenPGP
Arturo "Buanzo" Busleiman
The most widespread use of PGP and GnuPG is privacy related: People use it to encrypt email so only the recipient can read it. Some people use it to digitally sign files so those who receive those files can confirm they have not been altered in any way. Emails can be signed, too.
One night I suddenly woke up with the words "GET /" and "BEGIN PGP" in my head. And then it occurred to me: Why not use PGP to digitally sign HTTP requests so that Web servers can authenticate the identity and the integrity of the requests they receive? If you're interested in how I did this, keep reading.
OpenPGP: The Basics
OpenPGP is an Internet standard currently defined in RFC 2440. Applications that follow that standard are compatible with each other at the data/protocol level. This means that a file encrypted using GnuPG can be decrypted by PGP, and vice versa. Basically, OpenPGP defines two important operations: encryption and signing. The counterpart of those actions are decryption and verification, respectively. They can also be combined. That is, you can send an encrypted and signed email (or file) whose recipient will decrypt and verify at a later time.
The idea behind OpenPGP is that each person creates a pair of keys: One is public, the other is private. You can share the public one as much as you want, and it can also be uploaded to public key servers so that other OpenPGP applications can download public keys easily. But, the private key is a whole different story. You must keep it safe; you must also enter a secret passphrase so that the private key is also encrypted. When you want to sign or encrypt, you'll need the private key, so your passphrase will be requested. The public key is used for actions related to third parties; you use other people's public keys when you want to send encrypted emails to them or verify data sent by them.
If Alice sends a signed message to Bob, then Bob needs Alice's public key to verify the authenticity of the sender (i.e., Alice) and the data (i.e., the message). Alice needs to enter her passphrase at the time she signs the message. Bob needs no passphrase, because verification needs no private key. If the message is also encrypted, then Bob needs to enter his passphrase. That's why this article focuses on signing. Next, let's look at the HTTP protocol.
The HTTP Protocol
The HyperText Transfer Protocol version 1.1, defined by RFC 2616, declares that "request-header fields allow the client to pass additional information about the request, and about the client itself, to the server". That means that if one desires to add any kind of extra information, like an OpenPGP signature, request headers would be the place to do it. For example, a typical HTTP request using the GET method may look like this:
GET /login.php?user=buanzo&pass=2600 HTTP/1.1
Host: www.buanzo.com.ar
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.2) \
Gecko/20070226 Firefox/2.0.0.2
Referer: http://www.buanzo.com.ar/
This HTTP request uses the GET verb to request the /login.php file from the Web server at www.buanzo.com.ar using HTTP 1.1. It also provides login.php, a PHP script, with username and password values. It also says the browser is Mozilla Firefox 2.0.0.2, running under GNU/Linux. It says that the request was initiated (probably) by filling in a login form at http://www.buanzo.com.ar's index page. Of course, that's quite a stripped-down version of a real HTTP request, which usually has a dozen or more headers.
The server will provide an HTTP response to that request. The response is a set of headers, then a blank line, followed by data (HTML, MIME parts, etc.).
Extending HTTP with OpenPGP
This was my dream: To sign HTTP requests sent to a Web server, using an OpenPGP compatible program such as GnuPG and allow the server to verify the request, thus authenticating the sender's identity and message.
So, that's what this article is all about. I've developed a Mozilla Firefox extension, called Enigform and an Apache Module called mod_auth_openpgp. The name Enigform is a tribute to Mozilla Thunderbird's extension for OpenPGP support, Enigmail.
In a nutshell, Enigform can take GET and POST requests and sign them. Even requests generated via AJAX get to be signed. That is a real enhancement over the state of the art of Web 2.0 technology.
If your platform can run GnuPG and Mozilla Firefox, then you can use Enigform. Once Enigform is configured, you can tell it to Sign All Requests, or only sign those requests that a site specifically requires to be signed. Any request sent to a URL that ends with a specially named anchor (##ENIGFORM_Sign##) is signed by Enigform before sending. Of course, your passphrase is required, and you can tell Enigform to remember it for a limited time.
Additionally, one GnuPG feature called the GPG-Agent can be used by Enigform, relaying the passphrase handling to the gpg-agent. That is the recommended setting, because many other applications (like Enigmail for Thunderbird) take advantage of it, too, if it's available. This way you not only avoid typing your long, complex, and time-consuming passphrase over and over, but you also avoid having each application store the passphrase in its own memory space. Remember the simple HTTP request of the previous page? This is how it would look if it were signed:
GET /login.php?user=buanzo&pass=2600 HTTP/1.1
Host: www.buanzo.com.ar
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.2) \
Gecko/20070226 Firefox/2.0.0.2
Referer: http://www.buanzo.com.ar/
X-OpenPGP-Type: S
X-OpenPGP-Sig-Fields: body
X-OpenPGP-Sig: iD8DBQFGKQDIAlpOsGhXcE0RApmdAJ4yrOLyYUT7pi8Yzfy0q8DwfFWkeACfS \
Vl1dUEp2MWK+Z+AKFgJSiDnZnU==dLTH
X-OpenPGP-Digest-Algo: SHA1
X-OpenPGP-Version: GnuPG v1.4.6 (GNU/Linux)
X-OpenPGP-Agent: Enigform 0.8.0 for Mozilla Firefox
As you can see, several HTTP headers are appended to the request. That's what Enigform does. It appends request headers, nothing more. It is fully compatible with the HTTP 1.1 specification, and any Web and proxy server that speaks HTTP 1.1 would still understand it (although the OpenPGP headers are ignored). This, however, changes if your Apache Web server has mod_auth_openpgp loaded and enabled for that specific virtual host.
Before getting into mod_auth_openpgp, let's take a closer look at those X-OpenPGP headers. The first header, "X-OpenPGP-Type" has the value "S", which means "Signed". It tells the Web server that the request is "Signed" with OpenPGP. In the future (around Enigform 1.0.0), "Encryption" will be supported as well, so values such as "E" (Encrypted) or "SE" (Signed and Encrypted) will be supported.
Next, there is the "X-OpenPGP-Sig-Fields" header. In this case, "body" means the QUERY STRING (user=buanzo&pass=2600) for a GET request. For a POST request, it is the POST body (i.e., anything after the first blank line following the request headers). The header tells mod_auth_openpgp which pieces of data (and in what order) produce the "message" the signature corresponds to. The signature is stored in the X-OpenPGP-Sig header. In the future, Enigform will also support other fields, such as the value of other requests headers or cookies, for example.
With those three headers and the "message", mod_auth_openpgp can TEST and potentially verify the signature (more on that later). The rest of the fields are informative ones. "Digest-Algo" says which hashing algorithm is used. Mod_auth_openpgp does not need it, but another application might benefit from having this value. "Version" is the OpenPGP application in use. The Agent is Enigform itself. In the future, this could be "Mozilla Firefox" (hint, hint), or even disappear altogether.
But let's go even deeper. Remember I mentioned this sample HTTP request was a login request? Username and Password were sent to http://www.buanzo.com.ar/login.php. The script checks those values against a database (or whatever) and comes up with a "welcome" or "go to hell!" answer. In the "welcome" case, a session is initiated, by means of cookies, etc.
Do you think that a session is needed at all now that each request is signed? Not at all! For every request, mod_auth_openpgp can allow or deny access. Unsigned requests never get to the Web application. Of course, that is configurable. Additionally, having a username and password makes no sense at all, because the fact that the request is verified means that the data in the request is not modified and that we know WHO sent the request. But, wait! Aren't usernames and passwords those things thieves want to obtain when they set up phishing sites? Hmmm ... now we are getting somewhere, aren't we?
Apache Support for Enigform
Although those headers and the message can be verified from within a Python/PHP/Ruby script, having direct support within Apache for this new authentication scheme is definitely a big plus. That's why mod_auth_openpgp was created. This module can be enabled for a certain virtual host or directory. When it sees a signed request, it can verify it, but only if the matching public key is stored in the local public key ring. Although it is still a work in progress (at the time of writing mod_auth_openpgp is at version 0.2.0), it supports verification of signed GET and POST requests and even files uploaded via POST.
Upon successful verification, mod_auth_openpgp (mao) adds certain HTTP headers that can then be used by the famous mod_access Apache module to Allow/Deny access to a certain Directory, VHost, or Location. Let's see what a verified HTTP request looks like:
GET /login.php?user=buanzo&pass=2600 HTTP/1.1
Host: www.buanzo.com.ar
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.2) \
Gecko/20070226 Firefox/2.0.0.2
Referer: http://www.buanzo.com.ar/
X-OpenPGP-Type: S
X-OpenPGP-Sig-Fields: body
X-OpenPGP-Sig: iD8DBQFGKQDIAlpOsGhXcE0RApmdAJ4yrOLyYUT7pi8Yzfy0q8DwfFWkeACfS \
Vl1dUEp2MWK+Z+AKFgJSiDnZnU==dLTH
X-OpenPGP-Digest-Algo: SHA1
X-OpenPGP-Version: GnuPG v1.4.6 (GNU/Linux)
X-OpenPGP-Agent: Enigform 0.8.0 for Mozilla Firefox
X-Auth-OpenPGP = true
X-Auth-OpenPGP-Fingerprint = \
7350B06E7AEA78FDE739F3AD025A4EB06857704D
X-Auth-OpenPGP-Email = buanzo@buanzo.com.ar
X-Auth-OpenPGP-Name = Arturo Alberto Busleiman
X-Auth-OpenPGP-Comment = aka Buanzo
As you can see, the X-Auth-OpenPGP header and its siblings hold important information about the status and identity of the request. X-Auth-OpenPGP=true tells Apache that the request was correctly verified to belong to a certain individual and that the request was not tampered with during its voyage from the user's browser to the Web server. Then we have the -Fingerprint, -Email, -Name, and -Comment headers. The most useful are Fingerprint and Email.
This is an example of an Apache VirtualHost configuration that only lets buanzo@buanzo.com.ar access the private directory:
<VirtualHost *:80>
ServerName www.buanzo.com.ar
ServerAdmin root@localhost
DocumentRoot "/var/www/buanzo.com.ar/htdocs"
OpenPGPEngine on
SetEnvIf X-Auth-OpenPGP-Email buanzo@buanzo.com.ar valid_user
<Directory "/var/www/buanzo.com.ar/htdocs/PRIVATE">
Order Deny,Allow
Deny from all
Allow from env=valid_user
</Directory>
</VirtualHost>
Although that VirtualHost declaration is far from being a complete site configuration, it shows the minimal code needed to enable OpenPGP for a virtual host. Check out the mod_auth_openpgp package for more details.
In the block above, we make use of mod_access's "Allow from env" directive to only let the user buanzo@buanzo.com.ar into the /PRIVATE directory. SetEnvIf checks the X-Auth-OpenPGP-Email HTTP header, which would be added by mod_auth_openpgp upon verification of the signature. If it matches buanzo@buanzo.com.ar, it sets valid_user.
Use Cases and Ideas
What would be the real-life benefits of such a scheme? Simon Josefsson, an expert in the TLS, GSS, OpenPGP, and NTLM fields, says, "What you write in a HTML form is signed by Enigform, and the server knows who wrote it, and there is persistent evidence of it. Imagine Debian votes through the Web instead of via e-mail!" I believe phishing and man in the middle attacks could be rendered obsolete by Enigform.
One of the things I expect upon publication of this article is community contributions. Enigform and mod_auth_openpgp need more features and more OpenPGP/GnuPG experts to help make it a more secure platform. For example, better access control directives, automatic public key download, key ring path specification, etc.
On the other hand, many of you are probably wondering "Why OpenPGP instead of SSL?" Who said you can't use PGP-signed requests over HTTPS? For example, you might have created your own self-signed certificates for your Apache Web server, thus establishing a secure channel between your users and your secure Web site. Your users could use Enigform to add identity and data authentication to requests that are sent over an SSL channel. That is, SSL is a session protocol. OpenPGP can be used arbitrarily during that SSL session. The cost of SSL certificates, and how they are sold by "number of bits", is also worth mentioning. Enigform will support request encryption in the future, and mod_auth_openpgp will also sign and/or encrypt HTTP responses.
Another interesting approach has to do with the fact that you cannot assign SSL certificates for name-based virtual hosts: You need one real public IP for each SSL-enabled site. Enigform/mod_auth_openpgp does not have that limitation. It means you do not need extra public IP addresses and certificates, so you save money. Additionally, when Enigform supports encryption, it will be useful in situations where an SSL channel is not reliable or not desired (low latency situations, unreliable proxies, etc.). The time to set up an SSL channel is quite long compared to sending a low number of signed/encrypted HTTP requests. In other words, Enigform is an alternative to SSL, and its usage will depend on factors that I cannot foresee.
Final Words
This level of security can be overkill for certain configurations, but flexibility and security is an equation that is not easily balanced. The standardization process can help in that aspect, and that's why I'm working on an Internet draft to submit to the Internet Engineering Task Force so they can consider it for standardization. I think that if it becomes part of a standards track, I can cease developing Enigform and just focus on mod_auth_openpgp. If Web browsers implement OpenPGP support natively, then there is no need for plugins that get in the middle, like Enigform, or other obscure or insecure solutions like a "Signing Proxy". I really see this as the future of Web authentication. I would like to thank Sys Admin magazine and the whole Internet community for their support toward this goal.
Project Links
Author's consulting blog -- http://linux-consulting.buanzo.com.ar
Enigform on MozDev -- http://enigform.mozdev.org
Mod_auth_openpgp project on Freshmeat -- http://freshmeat.net/projects/maopenpgp
Enigform project on Freshmeat -- http://freshmeat.net/projects/enigform
Mozilla add-on page for Enigform -- https://addons.mozilla.org/en-US/firefox/addon/4531
Related Links
Smutty framework home page -- http://smutty.pu-gh.com/
Author's home page -- http://www.buanzo.com.ar/eng/
Buanzo is an Argentinean independent security consultant, programmer, and GNU/Linux enthusiast since 1994. He loves writing and playing with his punk rock band. He has contributed to many OSS projects, like Nmap, SANS TOP-20 2004, 2005 and 2006, and OISSG's Information Systems Security Assessment Framework book, while still finding time to make his son and wonderful wife happy. Buanzo can be reached through his Web site at: http://linux-consulting.buanzo.com.ar.
|