Web Services Security - Part 2: Encryption

by Ulf Dittmer

In a previous article I wrote about providing authentication for web services (WS) using the WS-Security standard as implemented by the Apache Axis SOAP stack. Besides authentication, WS-Security also provides encryption and digital signatures. In this article, I'll describe how to secure WS calls by encrypting their payload.

This is facilitated by way of the XML-Encryption standard –the details of which need not concern us–, which can be used to encrypt part or all of any XML document, not just SOAP.

As in the authentication article, the example I'm using is the Fibonacci calculator, a mathematical function whose deeper meaning isn't relevant here. All you need to know is that it takes a single integer parameter and returns a single integer result, e.g. Fibonacci(15) = 610.

Setting up the environment

The accompagnying zip file has everything you need to run the examples on the client side, but you'll also need Axis2 and its WS-Security module (called Rampart) installed and working. I also recommend that you at least glance at the previous article, which goes into a bit more detail on how to set up Axis and Rampart.

Start by copying the FibonacciService.aar and FibonacciServiceSymmetric.aar files in the build directory into the services directory of your Axis installation.

We'll also need to install the encryption libraries. While Java has built-in encryption (in the shape of the JCE API), suitable versions of that aren't available everywhere due to export restrictions for cryptological code. So instead of relying on the built-in, we'll use the BouncyCastle crypto provider, which implements JCE without any restrictions. The relevant library is part of the zip file; it's called bcprov-jdk15-141.jar. You'll need to copy that to the lib directory of the Axis web app. Now it's possible to tell the JVM to use that instead of Sun's crypto provider by calling:

java.security.Security.addProvider(
	new org.bouncycastle.jce.provider.BouncyCastleProvider());

It's also possible to install the BouncyCastle permanently in your JRE. That's done by changing the lib/security/java.security file within the JRE's directory. It contains a section that looks somewhat like this:

security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
security.provider.3=com.sun.net.ssl.internal.ssl.Provider
security.provider.4=com.sun.crypto.provider.SunJCE
security.provider.5=sun.security.jgss.SunProvider
security.provider.6=com.sun.security.sasl.Provider

If you add a line to it like the following (with the number before the equal sign being one higher than the highest existing one in the list), then it's not necessary to use the line of code shown above:

security.provider.7=org.bouncycastle.jce.provider.BouncyCastleProvider

Now there's one last step to do. Due to a bug in Axis2 SAAJ clients can't use WS-Security. The axis2-saaj-1.4.jar file in this distribution fixes the bug; you need to copy that into the lib directory of the Axis web app. Hopefully, the next version of Axis2 will include this fix.

Public and Private Encryption Keys

We can't avoid to talk briefly about how encryption works in general. There are broad kinds of encryption algorithms –or ciphers–, symmetric and asymmetric. Symmetric ciphers use the same key for encryption and decryption. That means both sides need to have it, and it needs to be kept secret, because anyone knowing the key can decrypt all messages encrypted with it. The standards DES (now obsolete) and AES are examples of symmetric ciphers. Asymmetric ciphers use two keys, a public one for encryption and a private one for decryption. The advantage is that there's no harm in communicating the public key to anyone, because it can't be used to decrypt anything. The private key, on the other hand, doesn't need to be sent to anyone, and is thus easier to keep secret. RSA is an example of an asymmetric cipher. These ciphers are generally much more compute-intensive (orders of magnitude slower), so they're rarely used to encrypt large messages.

The JRE includes the keytool executable to create and manage encryption keys; keys are stored in files called keystores. The keytool.sh script creates the keystore files we need for client and service. In its first lines it defines the encryption algorithm, the key length and how long the keys should be valid. The script runs on Unix-based operating systems, but a similar Windows batch file shouldn't be hard to create.

The Examples

The first example can be run by the command

ant test-axis2 -Dn=15

or by

ant test-axis2 -Dn=15 -Dport=8079

if you want to watch request and response with the tcpmon tool (which you need to start through ant tcpmon first). Here are examples what the request and response might look like. The specifics will differ for your installation, but it will be very similar.

The SOAP body of the request contains a big blob of incomprehensible data – the encrypted version of the request data, which is all of:

<ns1:calculateFibonacci xmlns:ns1="http://fibonacci/xsd">
	<in0>15</in0>
</ns1:calculateFibonacci>

The response is very similar; it contains the encrypted version of the response, which is really just:

<ns:calculateFibonacciResponse xmlns:ns="http://fibonacci/xsd">
	<ns:return>610</ns:return>
</ns:calculateFibonacciResponse>

How is this set up? It's the same conf/client.axis2.xml file used for the authentication. But this time, there's no UsernameToken action, but an Encrypt action (both can be used together, by the way). Here we have both OutflowSecurity (for service requests) and InflowSecurity (for service responses), because both directions should be encrypted. The details for both directions differ slightly, but they both tell Rampart which user and which key to use, and where to find those.

<parameter name="OutflowSecurity">
	<action>
		<items>Encrypt</items>
		<encryptionUser>service</encryptionUser>
		<encryptionPropFile>conf/client.properties</encryptionPropFile>
	</action>
</parameter>

<parameter name="InflowSecurity">
	<action>
		<items>Encrypt</items>
		<passwordCallbackClass>fibonacci.PWHandlerClient</passwordCallbackClass>
		<decryptionPropFile>conf/client.properties</decryptionPropFile>
	</action>
</parameter>

If you look at the transmitted SOAP closely (namely, the <xenc:EncryptionMethod> tags), you'll notice that for both request and response, the SOAP header is encrypted using RSA, while the SOAP body is encrypted using AES. This goes back to the earlier point about asymmetric algorithms (like RSA) being much slower than symmetric algorithms (like AES): RSA is used to encrypt an AES key, and nothing else. Then the AES key is used to encrypt the actual payload data.

There's also a very similar code example that uses a SAAJ client instead of an Axis2 client; (it can be run via ant test-saaj -Dn=15), but it works the same with respect to WS-Security, so I won't discuss it here.

Using a pre-determined encryption key

The second example uses a pre-determined key that both sides know in advance. It's run via

ant test-prekey -Dn=15 -Dport=8079

In this case, the encryption key is actually in the Java code, in the shape of the key[] array in the PWHandlerClient and PWHandlerServer classes. Here are the entries to set this up from the conf/client.axis2-prekey.xml file. The details of the outflow security are a bit different, because we're not using the service side's public key, but the key from the PWClientHandler class:

<parameter name="OutflowSecurity">
	<action>
		<items>Encrypt</items>
		<user>service</user>
		<encryptionPropFile>conf/client-prekey.properties</encryptionPropFile>
		<EmbeddedKeyCallbackClass>fibonacci.PWHandlerClient</EmbeddedKeyCallbackClass>
		<EmbeddedKeyName>SessionKey</EmbeddedKeyName>
	</action>
</parameter>

<parameter name="InflowSecurity">
	<action>
		<items>Encrypt</items>
		<passwordCallbackClass>fibonacci.PWHandlerClient</passwordCallbackClass>
		<decryptionPropFile>conf/client-prekey.properties</decryptionPropFile>
	</action>
</parameter>

If you look at the request and response, you'll see that the response is much shorter in this case. That's because the key needed for decryption is already known by the client (it's pre-determined, remember?), so the service doesn't need to send it.

When you run either of these example, you may be surprised about how long it takes until you get the response. There's no reason to worry about it in a production setting, though – for the first call, the JVM isn't warmed up, and especially the security code needs a lot of one-time setup. If you run several service calls in a row you'll notice that subsequent calls are much faster.

If –for some reason– you prefer to use encryption only in one direction, and not the other, it's perfectly possible to use only, say, inflow security for the service, and outflow security for the client. That would be an unusual setup, though – if the data is important enough to be encrypted in one direction, it's probably important enough to encrypt what is sent the other way, too.

Conclusion

Just like it was possible to replace HTTP authentication with WS-Security authentication, it's possible to replace the use of HTTPS with WS-Security encryption, thus moving it from the transport layer to the application layer. While it may seem that there's a lot of setup to do –especially if compared to simply using an HTTPS URL instead of HTTP URL– there are other benefits. HTTPS encryption ends the moment the request arrives at the server – it's gone by the time the request is being processed. With WS-Security, on the other hand, the data can be decrypted when appropriate. For instance, there may be several SOAP actors working on the message that don't need to see the contents; in that case it needn't be cleartext. Or the message can be stored and forwarded safely, because it's still encrypted.

In any case, applying XML-Encryption to web services isn't hard to set up, and it's the wave of the future. If data security is required, it should be considered for any service that's being put into production.